Que sont les lambdas de type Scala et quels sont leurs avantages?

Parfois, je tombe dans la notation semi-mystérieuse de

def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..} 

dans les articles du blog Scala, qui lui donnent une onde manuelle “nous avons utilisé ce type-lambda”.

Bien que j’aie une certaine idée à ce sujet (nous obtenons un paramètre de type anonyme A sans avoir à polluer la définition?), Je n’ai trouvé aucune source claire décrivant le type d’astuce lambda et ses avantages. Est-ce juste du sucre syntaxique ou ouvre-t-il de nouvelles dimensions?

Les lambdas de type sont souvent indispensables lorsque vous travaillez avec des types plus élevés.

Prenons un exemple simple de définition d’une monade pour la projection droite de Soit [A, B]. La classe de monade ressemble à ceci:

 trait Monad[M[_]] { def point[A](a: A): M[A] def bind[A, B](m: M[A])(f: A => M[B]): M[B] } 

Maintenant, Either 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 consiste à utiliser un type lambda:

 class EitherMonad[A] extends Monad[({type λ[α] = Either[A, α]})#λ] { def point[B](b: B): Either[A, B] def bind[B, C](m: Either[A, B])(f: B => Either[A, C]): Either[A, C] } 

Voici un exemple de curry dans le type system – vous avez choisi le type Either, de sorte que lorsque vous voulez créer une instance d’EitherMonad, vous devez spécifier l’un des types; l’autre est bien sûr fourni au moment où vous appelez ou reliez.

Le type lambda sortingck 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 lambda de type plus sophistiqué, ce qui est pénible à écrire en ligne. Voici un exemple de mon code d’aujourd’hui:

 // types X and E are defined in an enclosing scope private[iteratee] class FG[F[_[_], _], G[_]] { type FGA[A] = F[G, A] type IterateeM[A] = IterateeT[X, E, FGA, A] } 

Cette classe existe exclusivement pour que je puisse utiliser un nom comme FG [F, G] #IterateeM pour faire référence au type de la monade IterateeT spécialisée dans certaines versions de transformateurs d’une seconde monade spécialisée dans une troisième monade. Lorsque vous commencez à emstackr, ces types de constructions deviennent très nécessaires. Je n’instancie jamais un FG, bien sûr; c’est juste un hack pour me laisser exprimer ce que je veux dans le système de caractères.

Les avantages sont exactement les mêmes que ceux conférés par des fonctions anonymes.

 def inc(a: Int) = a + 1; List(1, 2, 3).map(inc) List(1, 2, 3).map(a => a + 1) 

Un exemple d’utilisation, avec Scalaz 7. Nous voulons utiliser un Functor capable de mapper une fonction sur le second élément d’un Tuple2 .

 type IntTuple[+A]=(Int, A) Functor[IntTuple].map((1, 2))(a => a + 1)) // (1, 3) Functor[({type l[a] = (Int, a)})#l].map((1, 2))(a => a + 1)) // (1, 3) 

Scalaz fournit des conversions implicites qui peuvent déduire l’argument de type à Functor , nous Functor donc souvent de les écrire complètement. La ligne précédente peut être réécrite comme:

 (1, 2).map(a => a + 1) // (1, 3) 

Si vous utilisez IntelliJ, vous pouvez activer Paramètres, Style de code, Scala, Pliage, Type Lambdas. Cela cache alors les parties creuses de la syntaxe , et présente le plus acceptable:

 Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3) 

Une future version de Scala pourrait prendre en charge directement une telle syntaxe.

Mettre les choses en contexte: cette réponse a été initialement publiée dans un autre sujet. Vous le voyez ici parce que les deux threads ont été fusionnés. L’énoncé de question dans ledit fil était comme suit:

Comment résoudre cette définition de type: Pure [({type? [A] = (R, a)}) #?]?

Quelles sont les raisons d’utiliser une telle construction?

Snipped vient de la bibliothèque scalaz:

 trait Pure[P[_]] { def pure[A](a: => A): P[A] } object Pure { import Scalaz._ //... implicit def Tuple2Pure[R: Zero]: Pure[({type ?[a]=(R, a)})#?] = new Pure[({type ?[a]=(R, a)})#?] { def pure[A](a: => A) = (Ø, a) } //... } 

Répondre:

 trait Pure[P[_]] { def pure[A](a: => A): P[A] } 

Le trait de soulignement dans les cases après que P implique que c’est un constructeur de type prend un type et renvoie un autre type. Exemples de constructeurs de types avec ce type: List , Option .

Donnez List un Int , un type concret, et cela vous donne List[Int] , un autre type concret. Give List a Ssortingng et il vous donne la List[Ssortingng] . Etc.

Donc, List , Option peut être considéré comme une fonction de type arity 1. Formellement, nous disons, ils ont un genre * -> * . L’astérisque indique un type.

Maintenant, Tuple2[_, _] est un constructeur de type avec kind (*, *) -> * c’est-à-dire que vous devez lui donner deux types pour obtenir un nouveau type.

Comme leurs signatures ne correspondent pas, vous ne pouvez pas remplacer Tuple2 par P Ce que vous devez faire est d’ appliquer partiellement Tuple2 sur l’un de ses arguments, ce qui nous donnera un constructeur de type avec kind * -> * , et nous pouvons le remplacer par P

Malheureusement, Scala n’a pas de syntaxe particulière 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 s’appellent cela parce qu’ils sont analogues aux expressions lambda qui existent au niveau de la valeur.

L’exemple suivant peut aider:

 // VALUE LEVEL // foo has signature: (Ssortingng, Ssortingng) => Ssortingng scala> def foo(x: Ssortingng, y: Ssortingng): Ssortingng = x + " " + y foo: (x: Ssortingng, y: Ssortingng)Ssortingng // world wants a parameter of type Ssortingng => Ssortingng scala> def world(f: Ssortingng => Ssortingng): Ssortingng = f("world") world: (f: Ssortingng => Ssortingng)Ssortingng // So we use a lambda expression that partially applies foo on one parameter // to yield a value of type Ssortingng => Ssortingng scala> world(x => foo("hello", x)) res0: Ssortingng = hello world // TYPE LEVEL // Foo has a kind (*, *) -> * scala> type Foo[A, B] = Map[A, B] defined type alias Foo // World wants a parameter of kind * -> * scala> type World[M[_]] = M[Int] defined type alias World // So we use a lambda lambda that partially applies Foo on one parameter // to yield a type of kind * -> * scala> type X[A] = World[({ type M[A] = Foo[Ssortingng, A] })#M] defined type alias X // Test the equality of two types. (If this comstacks, it means they're equal.) scala> implicitly[X[Int] =:= Foo[Ssortingng, Int]] res2: =:=[X[Int],Foo[Ssortingng,Int]] =  

Modifier:

Plus de valeur et de parallèles au niveau du type.

 // VALUE LEVEL // Instead of a lambda, you can define a named function beforehand... scala> val g: Ssortingng => Ssortingng = x => foo("hello", x) g: Ssortingng => Ssortingng =  // ...and use it. scala> world(g) res3: Ssortingng = hello world // TYPE LEVEL // Same applies at type level too. scala> type G[A] = Foo[Ssortingng, A] defined type alias G scala> implicitly[X =:= Foo[Ssortingng, Int]] res5: =:=[X,Foo[Ssortingng,Int]] =  scala> type T = World[G] defined type alias T scala> implicitly[T =:= Foo[Ssortingng, Int]] res6: =:=[T,Foo[Ssortingng,Int]] =  

Dans le cas que vous avez présenté, le paramètre de type R est local pour la fonction Tuple2Pure et vous ne pouvez donc pas simplement définir le type PartialTuple2[A] = Tuple2[R, A] , car il n’y a simplement aucun endroit où placer 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.)

 scala> type Partial2[F[_, _], A] = { | type Get[B] = F[A, B] | } defined type alias Partial2 scala> implicit def Tuple2Pure[R]: Pure[Partial2[Tuple2, R]#Get] = sys.error("") Tuple2Pure: [R]=> Pure[[B](R, B)] 

type World[M[_]] = M[Int] provoque implicitly[X[A] =:= Foo[Ssortingng,Int]] ce que nous mettons en A dans X[A] implicitly[X[A] =:= Foo[Ssortingng,Int]] est toujours vrai, je suppose.