En algún momento me encuentro con la notación semi-misteriosa de
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
en las publicaciones de blog de Scala, que le dan una onda manual "utilizamos ese truco tipo lambda".
Si bien tengo algo de intuición sobre esto (¿obtenemos un parámetro de tipo anónimo A
sin tener que contaminar la definición con él?), No encontré una fuente clara que describa cuál es el truco de lambda de tipo y cuáles son sus beneficios. ¿Es solo azúcar sintáctico o abre algunas dimensiones nuevas?
Respuestas:
Las lambdas de tipo son vitales bastante tiempo cuando trabajas con tipos de clase superior.
Considere un ejemplo simple de definir una mónada para la proyección correcta de Cualquiera [A, B]. La clase de mónada se ve así:
Ahora, Oither es un constructor de tipo de dos argumentos, pero para implementar Monad, debe darle un constructor de tipo de un argumento. La solución a esto es usar un tipo lambda:
Este es un ejemplo de curry en el sistema de tipos: ha cursado el tipo de Either, de modo que cuando desea crear una instancia de EitherMonad, debe especificar uno de los tipos; el otro, por supuesto, se proporciona al momento de llamar al punto o al enlace.
El truco de tipo lambda explota el hecho de que un bloque vacío en una posición de tipo crea un tipo estructural anónimo. Luego usamos la sintaxis # para obtener un miembro de tipo.
En algunos casos, es posible que necesite lambdas de tipo más sofisticado que es difícil escribir en línea. Aquí hay un ejemplo de mi código de hoy:
Esta clase existe exclusivamente para que pueda usar un nombre como FG [F, G] #IterateeM para referirme al tipo de la mónada IterateeT especializada en alguna versión transformadora de una segunda mónada que está especializada en alguna tercera mónada. Cuando comienzas a apilar, este tipo de construcciones se vuelven muy necesarias. Nunca instanciaré un FG, por supuesto; solo está ahí como un truco para dejarme expresar lo que quiero en el sistema de tipos.
fuente
bind
método para suEitherMonad
clase. :-) Aparte de eso, si puedo canalizar a Adriaan por un segundo aquí, no estás usando tipos de tipo superior en ese ejemplo. Estás dentroFG
, pero no dentroEitherMonad
. Por el contrario, está utilizando constructores de tipos , que tienen kind* => *
. Este tipo es de orden 1, que no es "superior".*
era la orden 1, pero en cualquier caso Monad tiene el tipo(* => *) => *
. Además, notará que especifiqué "la proyección correcta deEither[A, B]
" - la implementación es trivial (¡pero un buen ejercicio si no lo ha hecho antes!)*=>*
más alto se justifica por la analogía de que no llamamos a una función ordinaria (que asigna funciones no funciones a funciones no, en otras palabras, valores simples a valores simples) función de orden superior.Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
Los beneficios son exactamente los mismos que los que confieren las funciones anónimas.
Un ejemplo de uso, con Scalaz 7. Queremos usar un
Functor
que pueda mapear una función sobre el segundo elemento en aTuple2
.Scalaz proporciona algunas conversiones implícitas que pueden inferir el argumento de tipo
Functor
, por lo que a menudo evitamos escribirlas por completo. La línea anterior se puede reescribir como:Si usa IntelliJ, puede habilitar Configuración, Estilo de código, Scala, Plegado, Tipo Lambdas. Esto luego oculta las partes crudas de la sintaxis y presenta las más sabrosas:
Una versión futura de Scala podría admitir directamente tal sintaxis.
fuente
(1, 2).map(a => a + 1)
en REPL: `<console>: 11: error: value map no es miembro de (Int, Int) (1, 2) .map (a => a + 1) ^`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:
Responder:
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
unInt
tipo concreto, y te daList[Int]
otro tipo concreto. DaList
unString
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
porP
. Lo que debe hacer es aplicar parcialmenteTuple2
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:
Editar:
Más nivel de valor y paralelos de nivel de tipo.
En el caso que haya presentado, el parámetro tipo
R
es local para funcionarTuple2Pure
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).
fuente
type World[M[_]] = M[Int]
hace que supongamos que todo lo que ponemosA
enX[A]
elimplicitly[X[A] =:= Foo[String,Int]]
siempre es cierto.fuente