Parfois, je tombe sur la notation semi-mystérieuse de
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
dans les articles de blog de Scala, qui lui donnent un «on a utilisé cette astuce de type lambda».
Bien que j'aie une certaine intutition à ce sujet (nous obtenons un paramètre de type anonyme A
sans avoir à polluer la définition avec lui?), Je n'ai trouvé aucune source claire décrivant ce qu'est l'astuce de type lambda, et quels sont ses avantages. Est-ce juste du sucre syntaxique ou ouvre-t-il de nouvelles dimensions?
Réponses:
Les lambdas de type sont essentiels une bonne partie du temps lorsque vous travaillez avec des types de type supérieur.
Prenons un exemple simple de définition d'une monade pour la projection droite de l'un ou l'autre [A, B]. La classe de types monade ressemble à ceci:
Maintenant, Sither est un constructeur de type de deux arguments, mais pour implémenter Monad, vous devez lui donner un constructeur de type d'un argument. La solution à cela consiste à utiliser un type lambda:
Ceci est un exemple de curry dans le système de types - vous avez curry le type de l'un ou l'autre, de sorte que lorsque vous voulez créer une instance de EitherMonad, vous devez spécifier l'un des types; l'autre bien sûr est fourni au moment où vous appelez point ou bind.
L'astuce de type lambda exploite le fait qu'un bloc vide dans une position de type crée un type structurel anonyme. Nous utilisons ensuite la syntaxe # pour obtenir un membre de type.
Dans certains cas, vous aurez peut-être besoin de lambdas de type plus sophistiqués qui sont pénibles à écrire en ligne. Voici un exemple de mon code d'aujourd'hui:
Cette classe existe exclusivement pour que je puisse utiliser un nom comme FG [F, G] #IterateeM pour désigner le type de la monade IterateeT spécialisée dans une version de transformateur d'une deuxième monade spécialisée dans une troisième monade. Lorsque vous commencez à empiler, ces types de constructions deviennent très nécessaires. Je n'ai jamais instancié un FG, bien sûr; c'est juste là pour me permettre d'exprimer ce que je veux dans le système de types.
la source
bind
méthode pour votreEitherMonad
classe. :-) En dehors de cela, si je peux canaliser Adriaan pendant une seconde ici, vous n'utilisez pas de types plus élevés dans cet exemple. Vous êtes dedansFG
, mais pas dedansEitherMonad
. Vous utilisez plutôt des constructeurs de type , qui ont kind* => *
. Ce type est d'ordre 1, ce qui n'est pas "supérieur".*
était d'ordre 1, mais en tout cas Monad a du genre(* => *) => *
. Aussi, vous noterez que j'ai spécifié "la bonne projection deEither[A, B]
" - l'implémentation est triviale (mais un bon exercice si vous ne l'avez pas fait auparavant!)*=>*
plus haut est justifié par l'analogie que nous n'appelons pas une fonction ordinaire (qui mappe des non fonctions à des non fonctions, en d'autres termes, des valeurs simples en valeurs simples) une fonction d'ordre supérieur.Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
Les avantages sont exactement les mêmes que ceux conférés par les fonctions anonymes.
Un exemple d'utilisation, avec Scalaz 7. Nous voulons utiliser un
Functor
qui peut mapper une fonction sur le deuxième élément d'unTuple2
.Scalaz fournit des conversions implicites qui peuvent déduire l'argument de type
Functor
, donc nous évitons souvent de les écrire complètement. La ligne précédente peut être réécrite comme:Si vous utilisez IntelliJ, vous pouvez activer Paramètres, Style de code, Scala, Pliage, Type Lambdas. Cela cache alors les parties cruelles de la syntaxe et présente les plus acceptables:
Une future version de Scala pourrait prendre en charge directement une telle syntaxe.
la source
(1, 2).map(a => a + 1)
dans REPL: `<console>: 11: error: value map n'est pas membre de (Int, Int) (1, 2) .map (a => a + 1) ^`Pour mettre les choses en contexte: cette réponse a été initialement publiée dans un autre fil de discussion. Vous le voyez ici parce que les deux threads ont été fusionnés. L'énoncé de question dans ledit fil était le suivant:
Répondre:
Le trait de soulignement dans les cases après
P
implique qu'il s'agit d'un constructeur de type qui prend un type et renvoie un autre type. Exemples de constructeurs de types avec ce genre:List
,Option
.Donnez
List
unInt
, un type concret, et cela vous donneList[Int]
un autre type concret. DonnezList
unString
et ça vous donneList[String]
. Etc.Ainsi,
List
,Option
peuvent être considérées comme des fonctions de niveau du type d'arité 1. Formellement nous disons, ils ont une sorte* -> *
. L'astérisque désigne un type.Il
Tuple2[_, _]
s'agit maintenant d' un constructeur de type avec kind,(*, *) -> *
c'est-à-dire que vous devez lui donner deux types pour obtenir un nouveau type.Étant donné que leurs signatures ne correspondent pas, vous ne pouvez pas remplacer
Tuple2
parP
. Ce que vous devez faire est d' appliquer partiellementTuple2
sur l'un de ses arguments, ce qui nous donnera un constructeur de type avec kind* -> *
, et nous pouvons le remplacerP
.Malheureusement, Scala n'a pas de syntaxe spéciale pour l'application partielle des constructeurs de types, et nous devons donc recourir à la monstruosité appelée type lambdas. (Ce que vous avez dans votre exemple.) Ils sont appelés ainsi parce qu'ils sont analogues aux expressions lambda qui existent au niveau de la valeur.
L'exemple suivant peut vous aider:
Éditer:
Plus de parallèles de niveau de valeur et de niveau de type.
Dans le cas que vous avez présenté, le paramètre type
R
est local à la fonctionTuple2Pure
et vous ne pouvez donc pas simplement définirtype PartialTuple2[A] = Tuple2[R, A]
, car il n'y a tout simplement aucun endroit où vous pouvez mettre ce synonyme.Pour faire face à un tel cas, j'utilise l'astuce suivante qui utilise des membres de type. (J'espère que l'exemple est explicite.)
la source
type World[M[_]] = M[Int]
les causes que tout ce que nous mettons dansA
dansX[A]
l'implicitly[X[A] =:= Foo[String,Int]]
est toujours vrai guess I.la source