Para poner las cosas en contexto: esta respuesta se publicó originalmente en otro hilo. Lo está viendo aquí porque los dos hilos se han fusionado. La declaración de preguntas en dicho hilo fue la siguiente:
¿Cómo resolver esta definición de tipo: Pure [({type? [A] = (R, a)}) #?]?
¿Cuáles son las razones de usar tal construcción?
Snipped proviene de la biblioteca 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)
}
//...
}
Responder:
trait Pure[P[_]] {
def pure[A](a: => A): P[A]
}
El subrayado en los cuadros siguientes P
implica que es un constructor de tipos que toma un tipo y devuelve otro tipo. Ejemplos de constructores de tipos con este tipo: List
,Option
.
Dale List
un Int
tipo concreto, y te da List[Int]
otro tipo concreto. Da List
un String
y te daList[String]
. Etc.
Así que, List
, Option
puede ser considerado como funciones de nivel de tipo de aridad 1. Formalmente se dice, tienen una especie* -> *
. El asterisco denota un tipo.
Ahora Tuple2[_, _]
es un constructor de tipo con kind(*, *) -> *
es decir, debe darle dos tipos para obtener un nuevo tipo.
Ya que sus firmas no coinciden, no se puede sustituir Tuple2
por P
. Lo que debe hacer es aplicar parcialmente Tuple2
uno de sus argumentos, lo que nos dará un constructor de tipo con kind * -> *
, y podemos sustituirlo porP
.
Desafortunadamente, Scala no tiene una sintaxis especial para la aplicación parcial de constructores de tipos, por lo que debemos recurrir a la monstruosidad llamada tipo lambdas. (Lo que tiene en su ejemplo.) Se llaman así porque son análogos a las expresiones lambda que existen a nivel de valor.
El siguiente ejemplo podría ayudar:
// 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>
Editar:
Más nivel de valor y paralelos de nivel de tipo.
// 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>
En el caso que haya presentado, el parámetro tipo R
es local para funcionar Tuple2Pure
y, por lo tanto, no puede simplemente definirtype PartialTuple2[A] = Tuple2[R, A]
, porque simplemente no hay lugar donde pueda poner ese sinónimo.
Para tratar un caso así, utilizo el siguiente truco que hace uso de los miembros de tipo. (Esperemos que el ejemplo se explique por sí mismo).
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)]