O que são lambdas tipo em Scala e quais são os seus benefícios?

Às vezes eu tropeço na notação semi-misteriosa de

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

nas postagens do blog do Scala, que dão a ele um “usamos aquele truque do tipo lambda”.

Embora eu tenha alguma intuição sobre isso (ganhamos um parâmetro de tipo anônimo A sem ter que poluir a definição com ele?), Não encontrei nenhuma fonte clara descrevendo qual é o tipo de truque lambda e quais são seus benefícios. É apenas açúcar sintático ou abre novas dimensões?

Tipo lambdas são vitais um bom tempo quando você está trabalhando com tipos mais gentis.

Considere um exemplo simples de definir uma mônada para a projeção correta de [A, B]. A typadlass mônada se parece com isso:

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

Agora, Either é um construtor de tipo de dois argumentos, mas para implementar o Monad, você precisa dar a ele um construtor de tipo de um argumento. A solução para isso é usar um 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 é um exemplo de currying no sistema de tipos – você curry o tipo de Either, de forma que quando você deseja criar uma instância de EitherMonad, você precisa especificar um dos tipos; o outro, claro, é fornecido no momento em que você chama o ponto ou liga.

O truque do tipo lambda explora o fato de que um bloco vazio em uma posição de tipo cria um tipo estrutural anônimo. Em seguida, usamos a syntax # para obter um membro de tipo.

Em alguns casos, você pode precisar de lambdas de tipo mais sofisticado que são difíceis de escrever em linha. Aqui está um exemplo do meu código de hoje:

 // 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 class existe exclusivamente para que eu possa usar um nome como FG [F, G] #IterateeM para me referir ao tipo da menda IterateeT especializada em alguma versão de transformador de uma segunda mônada que é especializada em alguma terceira mônada. Quando você começa a empilhar, esses tipos de construções se tornam muito necessários. Eu nunca instancio um FG, claro; é apenas como um truque para me deixar expressar o que eu quero no sistema de tipos.

Os benefícios são exatamente os mesmos que aqueles conferidos por funções anônimas.

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

Um exemplo de uso, com o Scalaz 7. Queremos usar um Functor que possa mapear uma function sobre o segundo elemento em um 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) 

O Scalaz fornece algumas conversões implícitas que podem inferir o argumento de tipo para o Functor , então, muitas vezes, evitamos escrevê-las completamente. A linha anterior pode ser reescrita como:

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

Se você usar o IntelliJ, poderá habilitar Configurações, Estilo de Código, Scala, Dobra, Digitar Lambdas. Isso oculta as partes da syntax e apresenta as mais palatáveis:

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

Uma versão futura do Scala pode suportar diretamente essa syntax.

Para colocar as coisas no contexto: Essa resposta foi originalmente postada em outro tópico. Você está vendo isso aqui porque os dois segmentos foram mesclados. A instrução da questão no referido segmento era a seguinte:

Como resolver essa definição de tipo: Pure [({type? [A] = (R, a)}) #?]?

Quais são as razões de usar tal construção?

Recorte vem da 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) } //... } 

Responda:

 trait Pure[P[_]] { def pure[A](a: => A): P[A] } 

Aquele sublinhado nas checkboxs após P implica que é um construtor de tipo que recebe um tipo e retorna outro tipo. Exemplos de construtores de tipo com esse tipo: List , Option .

List an Int , um tipo concreto, e ele lhe dá List[Int] , outro tipo concreto. Dê List uma String e ela te da List[String] . Etc.

Portanto, List , Option pode ser considerada como funções de nível de tipo de aridade 1. Formalmente dizemos que elas têm um tipo * -> * . O asterisco indica um tipo.

Agora Tuple2[_, _] é um construtor de tipos com kind (*, *) -> * ou seja, você precisa dar dois tipos para obter um novo tipo.

Como as assinaturas não correspondem, você não pode replace Tuple2 por P O que você precisa fazer é aplicar parcialmente o Tuple2 em um de seus argumentos, o que nos dará um construtor de tipo com o tipo * -> * , e podemos substituí-lo por P

Infelizmente, o Scala não possui uma syntax especial para a aplicação parcial de construtores de tipos e, portanto, temos que recorrer à monstruosidade chamada lambdas tipo. (O que você tem em seu exemplo.) Eles são chamados porque são análogos às expressões lambda que existem no nível de valor.

O exemplo a seguir pode ajudar:

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

Editar:

Mais níveis de valor e paralelos de nível 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 =  // ...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]] =  scala> type T = World[G] defined type alias T scala> implicitly[T =:= Foo[String, Int]] res6: =:=[T,Foo[String,Int]] =  

No caso que você apresentou, o parâmetro de tipo R é local para a function Tuple2Pure e, portanto, você não pode simplesmente definir o type PartialTuple2[A] = Tuple2[R, A] , porque simplesmente não há lugar onde você possa colocar esse sinônimo.

Para lidar com esse caso, eu uso o seguinte truque que faz uso de membros do tipo. (Espero que o exemplo seja auto-explicativo).

 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] faz com que tudo o que colocamos em A em X[A] implicitly[X[A] =:= Foo[String,Int]] é sempre verdade, eu acho.