sintaxis programar introduccion instanciar crear clases clase scala types

programar - scala introduccion



¿Qué son las lambdas tipo en Scala y cuáles son sus beneficios? (4)

En algún momento me tropiezo con la notación semi-misteriosa de

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

en las publicaciones del blog de Scala, que le dan una onda de mano "usamos ese tipo-lambda truco".

Si bien tengo cierta intuición al respecto (ganamos un parámetro A tipo anónimo sin tener que contaminar la definición con él), no encontré una fuente clara que describa qué es el truco tipo lambda y cuáles son sus beneficios. ¿Es solo azúcar sintáctico, o abre algunas dimensiones nuevas?


Las lambdas de tipo son vitales bastante tiempo cuando trabajas con tipos de mayor nivel.

Considere un ejemplo simple de definir una mónada para la proyección correcta de Cualquiera [A, B]. La clase de tipo de mónada se ve así:

trait Monad[M[_]] { def point[A](a: A): M[A] def bind[A, B](m: M[A])(f: A => M[B]): M[B] }

Ahora bien, cualquiera de los dos es un constructor de tipos 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:

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] }

Este es un ejemplo de currying en el sistema de tipos: ha cursado el tipo de Cualquiera, de modo que cuando quiera crear una instancia de EitherMonad, tenga que especificar uno de los tipos; el otro, por supuesto, se suministra en el momento en que llamas al punto o en el enlace.

El truco 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 tipos de lambdas más sofisticados que son difíciles de escribir en línea. Aquí hay un ejemplo de mi código de hoy:

// 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] }

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 una versión transformadora de una segunda mónada que está especializada en una tercera mónada. Cuando comienzas a apilar, este tipo de construcciones se vuelven muy necesarias. Nunca instanciar un FG, por supuesto; simplemente está ahí como un truco para dejarme expresar lo que quiero en el sistema de tipos.


Los beneficios son exactamente los mismos que los otorgados por funciones anónimas.

def inc(a: Int) = a + 1; List(1, 2, 3).map(inc) List(1, 2, 3).map(a => a + 1)

Un ejemplo de uso, con Scalaz 7. Queremos usar un Functor que pueda mapear una función sobre el segundo elemento en 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 proporciona algunas conversiones implícitas que pueden inferir el argumento de tipo a Functor , por lo que a menudo evitamos escribirlas por completo. La línea anterior se puede reescribir como:

(1, 2).map(a => a + 1) // (1, 3)

Si usa IntelliJ, puede habilitar Configuraciones, Estilo de código, Escala, Plegable, Tipo Lambdas. Esto luego oculta las partes más difíciles de la sintaxis y presenta las más apetecibles:

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

Una versión futura de Scala podría ser compatible directamente con dicha sintaxis.


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 pregunta 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 de 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 guión bajo en las casillas después de 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 .

Da a List an Int , un tipo concreto, y te da List[Int] , otro tipo concreto. Dale a List una String y te da List[String] . Etc.

Entonces, List , Option se puede considerar como funciones de nivel de tipo de arity 1. Formalmente decimos que tienen un tipo * -> * . El asterisco denota un tipo.

Ahora Tuple2[_, _] es un constructor de tipos con kind (*, *) -> * es decir, necesitas darle dos tipos para obtener un nuevo tipo.

Como sus firmas no coinciden, no puede sustituir a Tuple2 por P Lo que debe hacer es aplicar parcialmente Tuple2 en uno de sus argumentos, lo que nos dará un constructor de tipo con kind * -> * , y podemos sustituirlo por P

Desafortunadamente, Scala no tiene una sintaxis especial para la aplicación parcial de constructores de tipo, por lo que debemos recurrir a la monstruosidad llamada tipo lambdas. (Lo que tienes en tu ejemplo.) Se llaman así porque son análogas 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 ha presentado, el parámetro de tipo R es local para la función Tuple2Pure y por lo tanto no puede simplemente definir el type PartialTuple2[A] = Tuple2[R, A] , porque simplemente no hay lugar donde puede poner ese sinónimo.

Para manejar este caso, utilizo el siguiente truco que hace uso de los miembros de tipo. (Afortunadamente, el ejemplo se explica 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)]


type World[M[_]] = M[Int] hace que todo lo que pongamos en A en X[A] implicitly[X[A] =:= Foo[String,Int]] sea ​​siempre verdadero, supongo.