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:
Comment résoudre cette définition de type: Pure [({type? [A] = (R, a)}) #?]?
Quelles sont les raisons d'utiliser une telle construction?
Snipped provient 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 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
un Int
, un type concret, et cela vous donne List[Int]
un autre type concret. Donnez List
un String
et ça vous donne List[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
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 P
.
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:
// 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>
Éditer:
Plus de parallèles de niveau de valeur et de niveau 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 type R
est local à la fonction 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ù 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.)
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)]