web-dev-qa-db-fra.com

Quels sont les lambdas de type dans Scala et quels sont leurs avantages?

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

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

dans Scala articles de blog, qui lui donnent une onde "nous avons utilisé cette astuce type-lambda".

Bien que j'aie quelques idées à 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 lambda de type et quels sont ses avantages. S'agit-il simplement de sucre syntaxique ou ouvre-t-il de nouvelles dimensions?

148
ron

Les lambdas de type sont essentiels un peu du temps lorsque vous travaillez avec des types de type supérieur.

Prenons un exemple simple de définition d'une monade pour la bonne projection de Soit [A, B]. La classe de types 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, Soit 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 est d'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]
}

Ceci est un exemple de curry dans le système de types - vous avez curry le type de Soit, de sorte que lorsque vous souhaitez 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 le point ou le liez.

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 difficiles à é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 afin 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 vers une version transformateur d'une deuxième monade qui est spécialisée dans une troisième monade. Lorsque vous commencez à empiler, ces types de constructions deviennent très nécessaires. Je n'instancie jamais un FG, bien sûr; il est juste là pour me permettre d'exprimer ce que je veux dans le système de type.

147
Kris Nuttycombe

Les avantages sont exactement les mêmes que ceux conférés par les 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 qui peut mapper une fonction sur le deuxième é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 type à Functor, donc nous évitons 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. Ceci alors cache les parties cruelles de la syntaxe , et présente le plus agréable au goût:

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

Une future version de Scala pourrait supporter directement une telle syntaxe.

52
retronym

Pour mettre les choses en contexte: Cette réponse a été initialement publiée dans un autre fil. Vous le voyez ici parce que les deux fils ont été fusionnés. L'énoncé de la question dans ledit fil était le suivant:

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éponse:

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

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 type avec ce type: List, Option.

Donnez à List un Int, un type concret, et cela vous donne List[Int], un autre type de béton. Donnez à List un String et cela vous donnera List[String]. Etc.

Ainsi, List, Option peuvent être considérés comme des fonctions de niveau arité 1. Formellement, nous disons qu'elles ont un type * -> *. L'astérisque indique un type.

À présent Tuple2[_, _] est 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 pour P. Ce que vous devez faire est appliquer partiellementTuple2 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 spéciale pour l'application partielle des constructeurs de type, 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 pourrait vous aider:

// VALUE LEVEL

// foo has signature: (String, String) => String
scala> def foo(x: String, y: String): String = x + " " + y
foo: (x: String, y: String)String

// world wants a parameter of type String => String    
scala> def world(f: String => String): String = f("world")
world: (f: String => String)String

// So we use a lambda expression that partially applies foo on one parameter
// to yield a value of type String => String
scala> world(x => foo("hello", x))
res0: String = 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[String, A] })#M]
defined type alias X

// Test the equality of two types. (If this compiles, it means they're equal.)
scala> implicitly[X[Int] =:= Foo[String, Int]]
res2: =:=[X[Int],Foo[String,Int]] = <function1>

Modifier:

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

// VALUE LEVEL

// Instead of a lambda, you can define a named function beforehand...
scala> val g: String => String = x => foo("hello", x)
g: String => String = <function1>

// ...and use it.
scala> world(g)
res3: String = hello world

// TYPE LEVEL

// Same applies at type level too.
scala> type G[A] = Foo[String, A]
defined type alias G

scala> implicitly[X =:= Foo[String, Int]]
res5: =:=[X,Foo[String,Int]] = <function1>

scala> type T = World[G]
defined type alias T

scala> implicitly[T =:= Foo[String, Int]]
res6: =:=[T,Foo[String,Int]] = <function1>

Dans le cas que vous avez présenté, le paramètre de type R est local pour fonctionner Tuple2Pure et vous ne pouvez donc pas simplement définir type PartialTuple2[A] = Tuple2[R, A], car il n'y a tout simplement aucun endroit où mettre ce synonyme.

Pour faire face à un tel cas, j'utilise l'astuce suivante qui utilise des membres de type. (Espérons que l'exemple s'explique de lui-même.)

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)]
40
missingfaktor

type World[M[_]] = M[Int] fait que tout ce que nous mettons dans A dans X[A] le implicitly[X[A] =:= Foo[String,Int]] est toujours vrai, je suppose.

0
wiesiu_p