De acordo com esta pergunta , o sistema de tipos de Scala é Turing completo . Quais recursos estão disponíveis para permitir que um recém-chegado aproveite o poder da programação em nível de tipo?
Aqui estão os recursos que encontrei até agora:
- A Alta Magia de Daniel Spiewak na Terra de Scala
- Programação de nível de tipo do Apocalisp em Scala
- Jesper's HList
Esses recursos são ótimos, mas sinto que não tenho o básico e, portanto, não tenho uma base sólida sobre a qual construir. Por exemplo, onde há uma introdução às definições de tipo? Que operações posso realizar nos tipos?
Existem bons recursos introdutórios?
Respostas:
Visão geral
A programação em nível de tipo tem muitas semelhanças com a programação tradicional em nível de valor. No entanto, ao contrário da programação de nível de valor, onde o cálculo ocorre em tempo de execução, na programação de nível de tipo, o cálculo ocorre em tempo de compilação. Tentarei traçar paralelos entre a programação no nível do valor e a programação no nível do tipo.
Paradigmas
Existem dois paradigmas principais na programação de nível de tipo: "orientada a objetos" e "funcional". A maioria dos exemplos vinculados a partir daqui seguem o paradigma orientado a objetos.
Um bom e bastante simples exemplo de programação em nível de tipo no paradigma orientado a objetos pode ser encontrado na implementação do cálculo lambda do Apocalisp , replicado aqui:
Como pode ser visto no exemplo, o paradigma orientado a objetos para programação em nível de tipo procede da seguinte forma:
trait Lambda
que existem os seguintes tipos que garantias:subst
,apply
, eeval
.trait App extends Lambda
parametrizados com dois tipos (S
eT
, ambos devem ser subtipos deLambda
),trait Lam extends Lambda
parametrizados com um tipo (T
) etrait X extends Lambda
(que não é parametrizado).#
(que é muito semelhante ao operador ponto:.
para valores). No traçoApp
do exemplo lambda cálculo, o tipoeval
é implementada da seguinte forma:type eval = S#eval#apply[T]
. Isso é essencialmente chamar oeval
tipo de parâmetro do traitS
e chamarapply
com parâmetroT
no resultado. Observe queS
é garantido ter umeval
tipo porque o parâmetro especifica que é um subtipo deLambda
. Da mesma forma, o resultado deeval
deve ter umapply
tipo, uma vez que é especificado como um subtipo deLambda
, conforme especificado no traço abstratoLambda
.O paradigma Funcional consiste em definir muitos construtores de tipo parametrizados que não são agrupados em características.
Comparação entre programação em nível de valor e programação em nível de tipo
abstract class C { val x }
trait C { type X }
C.x
(referenciando o valor do campo / função x no objeto C)C#x
(referenciando o tipo de campo x no traço C)def f(x:X) : Y
type f[x <: X] <: Y
(isso é chamado de "construtor de tipo" e geralmente ocorre no traço abstrato)def f(x:X) : Y = x
type f[x <: X] = x
a:A == b:B
implicitly[A =:= B]
assert(a == b)
implicitly[A =:= B]
A <:< B
, compila apenas seA
for um subtipo deB
A =:= B
, compila apenas seA
for um subtipo deB
eB
for um subtipo deA
A <%< B
, ("visível como") compila apenas seA
for visível comoB
(ou seja, há uma conversão implícita deA
para um subtipo deB
)Conversão entre tipos e valores
Em muitos dos exemplos, os tipos definidos por meio de características são frequentemente abstratos e selados e, portanto, não podem ser instanciados diretamente nem por meio de uma subclasse anônima. Portanto, é comum usar
null
como um valor de espaço reservado ao fazer um cálculo de nível de valor usando algum tipo de interesse:val x:A = null
, ondeA
está o tipo que você gostaDevido ao apagamento de tipo, os tipos parametrizados parecem todos iguais. Além disso, (como mencionado acima) os valores com os quais você está trabalhando tendem a ser todos
null
, portanto, o condicionamento no tipo de objeto (por exemplo, por meio de uma instrução de correspondência) é ineficaz.O truque é usar funções e valores implícitos. O caso base geralmente é um valor implícito e o caso recursivo é geralmente uma função implícita. Na verdade, a programação em nível de tipo faz uso intenso de implícitos.
Considere este exemplo ( tirado de metascala e apocalisp ):
Aqui você tem uma codificação peano dos números naturais. Ou seja, você tem um tipo para cada inteiro não negativo: um tipo especial para 0, a saber
_0
; e cada inteiro maior que zero tem um tipo da formaSucc[A]
, ondeA
é o tipo que representa um inteiro menor. Por exemplo, o tipo que representa 2 seria:Succ[Succ[_0]]
(sucessor aplicado duas vezes ao tipo que representa zero).Podemos criar um alias para vários números naturais para uma referência mais conveniente. Exemplo:
(Isso é muito parecido com definir um
val
como o resultado de uma função.)Agora, suponha que queremos definir uma função de nível de valor
def toInt[T <: Nat](v : T)
que recebe um valor de argumentov
,, que está em conformidade comNat
e retorna um inteiro que representa o número natural codificado nov
tipo de. Por exemplo, se temos o valorval x:_3 = null
(null
do tipoSucc[Succ[Succ[_0]]]
), gostaríamostoInt(x)
de retornar3
.Para implementar
toInt
, vamos fazer uso da seguinte classe:Como veremos a seguir, haverá um objeto construído a partir da classe
TypeToValue
para cada umNat
de_0
até (por exemplo)_3
, e cada um armazenará a representação do valor do tipo correspondente (ou sejaTypeToValue[_0, Int]
, armazenará o valor0
,TypeToValue[Succ[_0], Int]
armazenará o valor1
etc.). Nota,TypeToValue
é parametrizado por dois tipos:T
eVT
.T
corresponde ao tipo ao qual estamos tentando atribuir valores (em nosso exemplo,Nat
) eVT
corresponde ao tipo de valor que estamos atribuindo a ele (em nosso exemplo,Int
).Agora fazemos as seguintes duas definições implícitas:
E implementamos da
toInt
seguinte forma:Para entender como
toInt
funciona, vamos considerar o que ele faz em algumas entradas:Quando chamamos
toInt(z)
, o compilador procura um argumento implícitottv
do tipoTypeToValue[_0, Int]
(já quez
é do tipo_0
). Ele encontra o objeto_0ToInt
, ele chama ogetValue
método deste objeto e retorna0
. O ponto importante a notar é que não especificamos para o programa qual objeto usar, o compilador o encontrou implicitamente.Agora vamos considerar
toInt(y)
. Desta vez, o compilador procura um argumento implícitottv
do tipoTypeToValue[Succ[_0], Int]
(já quey
é do tipoSucc[_0]
). Ele encontra a funçãosuccToInt
, que pode retornar um objeto do tipo apropriado (TypeToValue[Succ[_0], Int]
) e o avalia. Essa função em si leva um argumento implícito (v
) do tipoTypeToValue[_0, Int]
(ou seja, aTypeToValue
onde o primeiro parâmetro de tipo tem um a menosSucc[_]
). O compilador fornece_0ToInt
(como foi feito na avaliaçãotoInt(z)
acima) esuccToInt
constrói um novoTypeToValue
objeto com valor1
. Novamente, é importante observar que o compilador está fornecendo todos esses valores implicitamente, já que não temos acesso a eles explicitamente.Verificando seu trabalho
Existem várias maneiras de verificar se seus cálculos em nível de tipo estão fazendo o que você espera. Aqui estão algumas abordagens. Faça dois tipos
A
eB
, que você deseja verificar são iguais. Em seguida, verifique se o seguinte compila:Equal[A, B]
Equal[T1 >: T2 <: T2, T2]
( retirado do apocolisp )implicitly[A =:= B]
Como alternativa, você pode converter o tipo em um valor (conforme mostrado acima) e fazer uma verificação de tempo de execução dos valores. Por exemplo
assert(toInt(a) == toInt(b))
, ondea
é do tipoA
eb
é do tipoB
.Recursos adicionais
O conjunto completo de construções disponíveis pode ser encontrado na seção de tipos do manual de referência do scala (pdf) .
Adriaan Moors tem vários artigos acadêmicos sobre construtores de tipos e tópicos relacionados com exemplos de scala:
Apocalisp é um blog com muitos exemplos de programação em nível de tipo em scala.
ScalaZ é um projeto muito ativo que fornece funcionalidade que estende a API Scala usando vários recursos de programação de nível de tipo. É um projeto muito interessante e com muitos seguidores.
MetaScala é uma biblioteca de nível de tipo para Scala, incluindo metatipos para números naturais, booleanos, unidades, HList, etc. É um projeto de Jesper Nordenberg (seu blog) .
O Michid (blog) tem alguns exemplos incríveis de programação em nível de tipo em Scala (de outra resposta):
Debasish Ghosh (blog) também tem algumas postagens relevantes:
(Tenho feito pesquisas sobre esse assunto e aqui está o que aprendi. Ainda sou novo nisso, então, aponte quaisquer imprecisões nesta resposta.)
fonte
Além dos outros links aqui, também há minhas postagens no blog sobre metaprogramação de nível de tipo em Scala:
fonte
Como sugerido no Twitter: Shapeless: Uma exploração da programação genérica / politípica em Scala por Miles Sabin.
fonte
fonte
Scalaz possui código fonte, wiki e exemplos.
fonte