Aqui, "valores", "tipos" e "tipos" têm significados formais, portanto, considerando seu uso comum em inglês ou analogias para classificar automóveis, você chegará até aqui.
Minha resposta diz respeito ao significado formal desses termos no contexto de Haskell especificamente; esses significados são baseados (embora não sejam realmente idênticos) aos significados usados na "teoria dos tipos" matemática / CS. Portanto, essa não será uma resposta muito boa da "ciência da computação", mas deve servir como uma resposta muito boa de Haskell.
No Haskell (e em outros idiomas), acaba sendo útil atribuir um tipo a uma expressão de programa que descreve a classe de valores que a expressão pode ter. Suponho aqui que você tenha visto exemplos suficientes para entender por que seria útil saber que, na expressão sqrt (a**2 + b**2)
, as variáveis a
e b
sempre serão valores do tipo Double
e não, digamos, String
e Bool
respectivamente. Basicamente, ter tipos nos ajuda a escrever expressões / programas que funcionarão corretamente em uma ampla gama de valores .
Agora, algo que você pode não ter percebido é que os tipos Haskell, como aqueles que aparecem nas assinaturas de tipo:
fmap :: Functor f => (a -> b) -> f a -> f b
na verdade, eles mesmos são escritos em uma sub-linguagem Haskell no nível de tipo. O texto do programa Functor f => (a -> b) -> f a -> f b
é - literalmente - uma expressão de tipo escrita nesta sub-linguagem. O sublíngua inclui operadores (por exemplo, ->
é um operador infixa associativos à direita nesta língua), variáveis (por exemplo, f
, a
, e b
), e "aplicação" de um tipo para outro expressão (por exemplo, f a
é f
aplicada a a
).
Mencionei como foi útil em muitas línguas atribuir tipos a expressões de programa para descrever classes de valores de expressão? Bem, nessa sub-linguagem de nível de tipo, as expressões são avaliadas para tipos (em vez de valores ) e acaba sendo útil atribuir tipos a expressões de tipo para descrever as classes de tipos que eles têm permissão para representar. Basicamente, ter tipos nos ajuda a escrever expressões de tipo que funcionarão corretamente em uma ampla variedade de tipos .
Assim, os valores são de tipos como tipos são os tipos e tipos nos ajudar a escrever valor programas -Level enquanto os tipos nos ajudar a escrever tipo programas -Level.
Como são esses tipos ? Bem, considere a assinatura do tipo:
id :: a -> a
Se a expressão de tipo a -> a
for válida, que tipo de tipos devemos permitir que a variável a
seja? Bem, as expressões de tipo:
Int -> Int
Bool -> Bool
parecem válidos, portanto os tipos Int
e Bool
são obviamente do tipo certo . Mas tipos ainda mais complicados, como:
[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]
parece válido. De fato, como devemos poder chamar id
funções, até:
(a -> a) -> (a -> a)
Parece bem. Assim, Int
, Bool
, [Double]
, Maybe [(Double,Int)]
, e a -> a
todo o olhar como tipos do direito tipo .
Em outras palavras, parece que existe apenas um tipo , vamos chamá-lo *
como um curinga do Unix, e todo tipo tem o mesmo tipo *
, fim da história.
Direita?
Bem, não exatamente. Acontece que Maybe
, por si só, é uma expressão de tipo tão válida quanto Maybe Int
(da mesma maneira sqrt
, por si só, é tão válida quanto uma expressão de valor sqrt 25
). No entanto , a seguinte expressão de tipo não é válida:
Maybe -> Maybe
Porque, embora Maybe
seja uma expressão de tipo, ela não representa o tipo de tipo que pode ter valores. Então, é assim que devemos definir *
- é o tipo de tipo que tem valores; inclui tipos "completos" como Double
ou Maybe [(Double,Int)]
mas exclui tipos incompletos e sem valor Either String
. Para simplificar, chamarei esses tipos completos de *
"tipos concretos", embora essa terminologia não seja universal, e "tipos concretos" podem significar algo muito diferente de, por exemplo, um programador de C ++.
Agora, na expressão tipo a -> a
, contanto que tipo a
tem tipo *
(o tipo de tipos de concreto), o resultado da expressão tipo a -> a
irá também tem tipo *
(ou seja, o tipo de tipos de concreto).
Então, que tipo de tipo é Maybe
? Bem, Maybe
pode ser aplicado a um tipo de concreto para produzir outro tipo de concreto. Assim, Maybe
parece um pouco como uma função de nível tipo que leva um tipo de tipo *
e retorna um tipo de espécie *
. Se tivéssemos uma função de nível de valor que pegasse um valor de tipo Int
e retornasse um valor de tipo Int
, atribuiríamos a ela uma assinatura de tipoInt -> Int
; portanto, por analogia, devemos atribuir Maybe
uma assinatura de tipo* -> *
. O GHCi concorda:
> :kind Maybe
Maybe :: * -> *
Retornando para:
fmap :: Functor f => (a -> b) -> f a -> f b
Nesta assinatura de tipo, variável f
tem tipo * -> *
e variáveis a
e b
tem tipo *
; o operador interno ->
possui tipo * -> * -> *
(pega um tipo *
à esquerda e outro à direita e retorna um tipo também *
). A partir disso e das regras de inferência de tipo, você pode deduzir que a -> b
é um tipo válido com tipo *
, f a
e f b
também são tipos válidos com tipo *
, e (a -> b) -> f a -> f b
é um tipo válido de tipo *
.
Em outras palavras, o compilador pode "verificar" a expressão de tipo (a -> b) -> f a -> f b
para verificar se é válida para variáveis de tipo do tipo certo da mesma forma que "verifica" sqrt (a**2 + b**2)
para verificar se é válida para variáveis do tipo certo.
A razão para usar termos separados para "tipos" versus "tipos" (ou seja, não falar sobre os "tipos de tipos") é principalmente apenas para evitar confusão. Os tipos acima parecem muito diferentes dos tipos e, pelo menos a princípio, parecem se comportar de maneira bem diferente. (Por exemplo, leva algum tempo para entender a idéia de que todo tipo "normal" tem o mesmo tipo *
e o tipo não a -> b
é .)*
* -> *
Parte disso também é histórica. À medida que o GHC Haskell evoluiu, as distinções entre valores, tipos e tipos começaram a desaparecer. Hoje em dia, os valores podem ser "promovidos" em tipos, e tipos e tipos são realmente a mesma coisa. Assim, no Haskell moderno, os valores têm tipos e ARE (quase), e os tipos são apenas mais tipos.
@ user21820 pediu uma explicação adicional de "tipos e tipos são realmente a mesma coisa". Para ser um pouco mais claro, no GHC Haskell moderno (desde a versão 8.0.1, acho), tipos e tipos são tratados de maneira uniforme na maioria dos códigos do compilador. O compilador faz algum esforço nas mensagens de erro para distinguir entre "tipos" e "tipos", dependendo se está reclamando sobre o tipo de um valor ou o tipo de um tipo, respectivamente.
Além disso, se nenhuma extensão estiver ativada, eles serão facilmente distinguíveis no idioma da superfície. Por exemplo, tipos (de valores) têm uma representação na sintaxe (por exemplo, em assinaturas de tipo), mas tipos (de tipos) são - eu acho - completamente implícitos, e não há sintaxe explícita onde eles apareçam.
Mas, se você ativar as extensões apropriadas, a distinção entre tipos e tipos desaparecerá amplamente. Por exemplo:
{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
Bar :: Bool -> * -> Foo
Aqui, Bar
é (um valor e) um tipo. Como um tipo, seu tipo é Bool -> * -> Foo
, que é uma função no nível de tipo que pega um tipo de tipo Bool
(que é um tipo, mas também um tipo) e um tipo de tipo *
e produz um tipo de tipo Foo
. Tão:
type MyBar = Bar True Int
verifica corretamente.
Como @AndrejBauer explica em sua resposta, essa falha na distinção entre tipos e tipos é insegura - ter um tipo / tipo *
cujo tipo / tipo é ele próprio (como é o caso da moderna Haskell) leva a paradoxos. No entanto, o sistema de tipos de Haskell já está cheio de paradoxos por causa da não terminação, por isso não é considerado um grande problema.