Per mettere le cose nel contesto: questa risposta è stata originariamente pubblicata in un'altra discussione. Lo vedi qui perché i due thread sono stati uniti. La dichiarazione della domanda in detto thread era la seguente:
Come risolvere questa definizione di tipo: Pure [({type? [A] = (R, a)}) #?]?
Quali sono i motivi per utilizzare tale costruzione?
Snipped viene dalla libreria 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)
}
//...
}
Risposta:
trait Pure[P[_]] {
def pure[A](a: => A): P[A]
}
Il carattere di sottolineatura nelle caselle dopo P
implica che si tratta di un costruttore di tipo accetta un tipo e ne restituisce un altro. Esempi di costruttori di tipi con questo tipo: List
, Option
.
Dai List
un Int
tipo concreto e ti dà List[Int]
un altro tipo concreto. Dai List
un String
e ti dà List[String]
. Eccetera.
Così, List
, Option
possono essere considerate funzioni di livello tipo di arietà 1. Formalmente diciamo, hanno una sorta * -> *
. L'asterisco indica un tipo.
Ora Tuple2[_, _]
è un costruttore di tipi con kind (*, *) -> *
cioè devi dargli due tipi per ottenere un nuovo tipo.
Dal momento che le loro firme non corrispondono, non è possibile sostituire Tuple2
per P
. Quello che devi fare è applicare parzialmente Tuple2
su uno dei suoi argomenti, che ci darà un costruttore di tipi con kind * -> *
, e possiamo sostituirlo P
.
Sfortunatamente Scala non ha una sintassi speciale per l'applicazione parziale dei costruttori di tipi, e quindi dobbiamo ricorrere alla mostruosità chiamata tipo lambdas. (Quello che hai nel tuo esempio.) Si chiamano così perché sono analoghi alle espressioni lambda che esistono a livello di valore.
Il seguente esempio potrebbe essere d'aiuto:
// 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>
Modificare:
Più parallelismi a livello di valore e di livello.
// 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>
Nel caso che hai presentato, il parametro type R
è locale per funzionare Tuple2Pure
e quindi non puoi semplicemente definirlo type PartialTuple2[A] = Tuple2[R, A]
, perché semplicemente non c'è posto dove puoi mettere quel sinonimo.
Per affrontare un caso del genere, utilizzo il seguente trucco che utilizza membri di tipo. (Speriamo che l'esempio sia autoesplicativo.)
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)]