À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 Scala, que fornecem uma onda manual "usamos o truque lambda de tipo".
Embora eu tenha alguma intuição sobre isso (obtemos um parâmetro de tipo anônimo A
sem precisar poluir a definição?), Não encontrei uma fonte clara descrevendo qual é o truque do tipo lambda e quais são seus benefícios. É apenas açúcar sintático, ou abre algumas novas dimensões?
Respostas:
As lambdas de tipo são vitais um pouco do tempo quando você está trabalhando com tipos de classe superior.
Considere um exemplo simples de definição de uma mônada para a projeção correta de Qualquer um [A, B]. A classe de mônada é assim:
Agora, Ou é um construtor de tipos de dois argumentos, mas para implementar o Monad, você precisa atribuir a ele um construtor de tipos de um argumento. A solução para isso é usar um tipo lambda:
Este é um exemplo de currying no sistema de tipos - você alterou o tipo de Either, de modo que, ao criar uma instância do EitherMonad, você deve especificar um dos tipos; o outro, é claro, é fornecido no momento em que você chama o ponto ou liga.
O truque lambda de tipo explora o fato de que um bloco vazio em uma posição de tipo cria um tipo estrutural anônimo. Em seguida, usamos a sintaxe # para obter um membro do tipo.
Em alguns casos, você pode precisar de lambdas de tipo mais sofisticado que sejam difíceis de escrever em linha. Aqui está um exemplo do meu código de hoje:
Esta classe existe exclusivamente para que eu possa usar um nome como FG [F, G] #IterateeM para me referir ao tipo de mônada IterateeT especializada em alguma versão transformadora de uma segunda mônada especializada em outra terceira mônada. Quando você começa a empilhar, esses tipos de construções se tornam muito necessários. Eu nunca instancia um GF, é claro; existe apenas como um hack para me deixar expressar o que eu quero no sistema de tipos.
fonte
bind
método para suaEitherMonad
classe. :-) Além disso, se eu puder canalizar Adriaan por um segundo aqui, você não está usando tipos mais altos nesse exemplo. Você está dentroFG
, mas não dentroEitherMonad
. Em vez disso, você está usando construtores de tipo , que são do tipo* => *
. Esse tipo é da ordem 1, que não é "superior".*
fosse da ordem 1, mas, de qualquer forma, Monad é gentil(* => *) => *
. Além disso, você vai notar que eu especifiquei "a projeção direitaEither[A, B]
" - (! Mas um bom exercício se você não tiver feito isso antes) a implementação é trivial*=>*
mais alto é justificado pela analogia de que não chamamos de função comum (que mapeia não funções para não funções, ou seja, valores simples para valores simples) função de ordem superior.Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
Os benefícios são exatamente os mesmos que os conferidos por funções anônimas.
Um exemplo de uso, com o Scalaz 7. Queremos usar um
Functor
que possa mapear uma função sobre o segundo elemento em aTuple2
.O Scalaz fornece algumas conversões implícitas que podem inferir o argumento de tipo
Functor
, portanto, muitas vezes evitamos escrevê-las completamente. A linha anterior pode ser reescrita como:Se você usa o IntelliJ, pode ativar Configurações, Estilo do código, Escala, Dobra, Tipo Lambdas. Isso então oculta as partes cruéis da sintaxe e apresenta as mais palatáveis:
Uma versão futura do Scala pode suportar diretamente essa sintaxe.
fonte
(1, 2).map(a => a + 1)
no REPL: `<console>: 11: error: value map não é membro de (Int, Int) (1, 2) .map (a => a + 1) ^`Para colocar as coisas em contexto: Esta resposta foi postada originalmente em outro tópico. Você está vendo aqui porque os dois threads foram mesclados. A declaração da pergunta no segmento mencionado foi a seguinte:
Responda:
O sublinhado nas caixas depois
P
implica que é um construtor de tipo pega um tipo e retorna outro tipo. Exemplos de construtores de tipos com este tipo:List
,Option
.Dê
List
umInt
, um tipo concreto, e isso lhe daráList[Int]
outro tipo concreto. DêList
umString
e dá-lheList[String]
. Etc.Assim,
List
,Option
pode ser considerado como funções de nível tipo de aridade 1. Formalmente dizemos, eles têm um tipo* -> *
. O asterisco indica um tipo.Agora
Tuple2[_, _]
é um construtor de tipos com tipo,(*, *) -> *
ou seja, você precisa atribuir dois tipos para obter um novo tipo.Desde suas assinaturas não corresponderem, você não pode substituir
Tuple2
paraP
. O que você precisa fazer é aplicar parcialmenteTuple2
em um de seus argumentos, o que nos dará um construtor de tipo com tipo* -> *
, e podemos substituí-loP
.Infelizmente, Scala não possui sintaxe especial para aplicação parcial de construtores de tipo, e, portanto, temos que recorrer à monstruosidade chamada tipo lambdas. (O que você tem no seu exemplo.) Eles são chamados assim porque são análogos às expressões lambda que existem no nível de valor.
O exemplo a seguir pode ajudar:
Editar:
Mais paralelos em nível de valor e nível de tipo.
No caso apresentado, o parâmetro type
R
é local para funcionarTuple2Pure
e, portanto, você não pode simplesmente definirtype PartialTuple2[A] = Tuple2[R, A]
, porque simplesmente não há lugar onde você possa colocar esse sinônimo.Para lidar com esse caso, use o seguinte truque que faz uso de membros do tipo. (Espero que o exemplo seja auto-explicativo.)
fonte
type World[M[_]] = M[Int]
faz com que tudo o que colocamos emA
noX[A]
aimplicitly[X[A] =:= Foo[String,Int]]
sempre é verdade, eu acho.fonte