Estou aprendendo o idioma Haskell, e estou tentando entender qual é a diferença entre a type
e a kind
.
Pelo que entendi a kind is a type of type
. Por exemplo, a ford is a type of car
e a car is a kind of vehicle
.
Essa é uma boa maneira de pensar sobre isso?
Porque, da maneira como meu cérebro está conectado, a ford is a **type** of car
, mas também por car is a **type** of vehicle
enquanto, ao mesmo tempo a car is a **kind** of vehicle
. Ou seja, os termos type
e kind
são intercambiáveis.
Alguém poderia lançar alguma luz sobre isso?
type-theory
Thomas Cook
fonte
fonte
Respostas:
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áveisa
eb
sempre serão valores do tipoDouble
e não, digamos,String
eBool
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:
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
, eb
), e "aplicação" de um tipo para outro expressão (por exemplo,f a
éf
aplicada aa
).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:
Se a expressão de tipo
a -> a
for válida, que tipo de tipos devemos permitir que a variávela
seja? Bem, as expressões de tipo:parecem válidos, portanto os tipos
Int
eBool
são obviamente do tipo certo . Mas tipos ainda mais complicados, como:parece válido. De fato, como devemos poder chamar
id
funções, até:Parece bem. Assim,
Int
,Bool
,[Double]
,Maybe [(Double,Int)]
, ea -> 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 quantoMaybe Int
(da mesma maneirasqrt
, por si só, é tão válida quanto uma expressão de valorsqrt 25
). No entanto , a seguinte expressão de tipo não é válida: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" comoDouble
ouMaybe [(Double,Int)]
mas exclui tipos incompletos e sem valorEither 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 tipoa
tem tipo*
(o tipo de tipos de concreto), o resultado da expressão tipoa -> 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 tipoInt
e retornasse um valor de tipoInt
, atribuiríamos a ela uma assinatura de tipoInt -> Int
; portanto, por analogia, devemos atribuirMaybe
uma assinatura de tipo* -> *
. O GHCi concorda:Retornando para:
Nesta assinatura de tipo, variável
f
tem tipo* -> *
e variáveisa
eb
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 quea -> b
é um tipo válido com tipo*
,f a
ef 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ãoa -> 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:
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 tipoBool
(que é um tipo, mas também um tipo) e um tipo de tipo*
e produz um tipo de tipoFoo
. Tão: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.fonte
type
é apenastype
ele mesmo, e não haveria necessidade dissokind
. Então, qual é exatamente a distinção?Bool
é um tipoType
é um tipo porque seus elementos são tiposBool -> Int
é um tipoBool -> Type
é um tipo porque seus elementos são funções que retornam tiposBool * Int
é um tipoBool * Type
é um tipo porque seus elementos são pares com um componente por tipo*
*
U_1
fonte
Type :: Type
é um axioma. A distinção entre "tipo" e "tipo" é inteiramente na linguagem humana, nesse caso.True
tem um tipo,,Bool
eBool
tem um tipoType
, que por si só tem tipoType
. Às vezes, chamamos um tipo de tipo, para enfatizar que é o tipo de uma entidade em nível de tipo, mas, em Haskell, ainda é apenas um tipo. Em um sistema em que universos realmente existem, como Coq, então "tipo" pode se referir a um universo e "tipo" a outro, mas geralmente queremos infinitamente muitos universos.Type :: Type
e uma distinção entre tipos e tipos. Além disso, que parte do código demonstraType :: Type
em Haskell?*
em Haskell há um tipo de universo. Eles simplesmente não chamam assim.Type
deData.Kinds
e*
deve ser sinônimo. Inicialmente, tínhamos apenas*
como primitivo, enquanto atualmente é definido internamente comoGHC.Types.Type
no módulo internoGHC.Types
, por sua vez definido comotype Type = TYPE LiftedRep
. Eu acho queTYPE
é o verdadeiro primitivo, fornecendo uma família de tipos (tipos levantados, tipos fora da caixa, ...). A maior parte da complexidade "deselegante" aqui é suportar algumas otimizações de baixo nível, e não por razões teóricas de tipo real.v
é um valor, então ele tem um tipo:v :: T
. SeT
é um tipo, então ele tem um tipo:T :: K
. O tipo de tipo é chamado de tipo. Tipos que se parecemTYPE rep
podem ser chamados de tipos, embora a palavra seja incomum. IffT :: TYPE rep
éT
permitido aparecer no RHS de a::
. A palavra "tipo" tem nuances:K
inT :: K
é uma espécie, mas não év :: K
, embora seja a mesmaK
. Poderíamos definir "K
é um tipo se seu tipo é um tipo" aka "os tipos estão no RHS de::
", mas isso não captura o uso corretamente. Portanto, minha posição de "distinção humana".Um valor é como o Ford Mustang 2011 vermelho específico, com 19.206 milhas que você está sentado na sua garagem.
Esse valor específico, informalmente, pode ter muitos tipos : é um Mustang, é um Ford, é um carro e é um veículo, entre muitos outros tipos que você pode inventar (o tipo de "coisas"). pertencente a você "ou o tipo de" coisas vermelhas "ou ...).
(Em Haskell, para uma aproximação de primeira ordem (os GADTs quebram essa propriedade, e a mágica em torno dos literais numéricos e a extensão OverloadedStrings a obscurecem um pouco)), os valores têm um tipo principal em vez da infinidade de "tipos" informais que você pode dar " stang.
42
é, para os fins desta explicação, umInt
; não existe nenhum tipo em Haskell para "números" ou "números inteiros" - ou melhor, você pode criar um, mas seria um tipo separado deInt
.)Agora, "Mustang" pode ser um subtipo de "carro" - todo valor que é um Mustang também é um carro. Mas o tipo - ou, para usar a terminologia de Haskell, o tipo de "Mustang" não é "carro". "Mustang" do tipo não é algo que você pode estacionar na entrada da sua casa ou dirigir. "Mustang" é um substantivo, uma categoria ou apenas um tipo. Esses são, informalmente, os tipos de "Mustang".
(Novamente, Haskell reconhece apenas um tipo para cada coisa no nível de tipo. Assim,
Int
tem tipo*
, e nenhum outro tipo.Maybe
Tem tipo* -> *
e não há outros tipos. Mas a intuição ainda deve se manter:42
é umInt
, e você pode fazerInt
coisas com ele como adicionar e subtrair. emInt
si não é umInt
; não existe um número comoInt + Int
. Você pode ouvir informalmente as pessoas dizerem queInt
é aNum
, pelo que elas significam que há uma instância daNum
classe de tipo para o tipoInt
- isso não é a mesma coisa como dizendo queInt
tem tipoNum
.Int
tem tipo "tipo", que em Haskell está escrito*
.)Portanto, todo "tipo" informal não é apenas um substantivo ou uma categoria? Todos os tipos têm o mesmo tipo? Por que falar sobre os tipos se eles são tão chatos?
É aqui que a analogia em inglês fica um pouco complicada, mas tenha paciência comigo: finja que a palavra "proprietário" em inglês não fazia sentido isoladamente, sem uma descrição da propriedade. Finja que se alguém o chamou de "proprietário", isso não faria sentido para você; mas se alguém o chamar de "proprietário de carro", você poderá entender o que eles significam.
"Proprietário" não tem o mesmo tipo de "carro", porque você pode falar sobre um carro, mas não pode falar sobre um proprietário nesta versão inventada do inglês. Você só pode falar sobre um "proprietário de carro". "Proprietário" cria apenas algo do tipo "substantivo" quando aplicado a algo que já possui o "nome" do tipo, como "carro". Diríamos que o tipo de "proprietário" é "substantivo -> substantivo". "Proprietário" é como uma função que pega um substantivo e produz a partir dele um substantivo diferente; mas não é um substantivo em si.
Observe que "proprietário do carro" não é um subtipo de "carro"! Não é uma função que aceita ou devolve carros! É apenas um tipo completamente separado de "carro". Ele descreve valores com dois braços e duas pernas que, a certa altura, tinham uma certa quantia de dinheiro e levaram esse dinheiro a uma concessionária. Não descreve valores com quatro rodas e uma pintura. Observe também que "proprietário do carro" e "proprietário do cachorro" são tipos diferentes, e coisas que você pode querer fazer com um podem não ser aplicáveis ao outro.
(Da mesma forma, quando dizemos que
Maybe
é gentil* -> *
em Haskell, queremos dizer que não faz sentido (formalmente; informalmente fazemos isso o tempo todo) falar em ter "aMaybe
". Em vez disso, podemos ter aMaybe Int
ou aMaybe String
, pois essas são coisas de tipo*
.)Portanto, o objetivo principal de falar sobre tipos é para que possamos formalizar nosso raciocínio em torno de palavras como "proprietário" e fazer cumprir que apenas aceitemos valores de tipos que foram "totalmente construídos" e que não são absurdos.
fonte
Isso mesmo - então vamos explorar o que isso significa.
Int
ouText
são tipos concretos, masMaybe a
é um tipo abstrato . Não se tornará um tipo concreto até que você decida qual valor específico desejaa
para uma determinada variável (ou valor / expressão / qualquer que seja), por exemploMaybe Text
.Dizemos que
Maybe a
é um construtor de tipos porque é como uma função que pega um único tipo de concreto (por exemploText
) e retorna um tipo de concreto (Maybe Text
neste caso). Mas outros construtores de tipos podem usar ainda mais "parâmetros de entrada" antes de retornar um tipo concreto. por exemplo,Map k v
precisa usar dois tipos de concreto (por exemplo,Int
eText
) antes de poder construir um tipo de concreto (Map Int Text
).Portanto, os construtores de tipo
Maybe a
eList a
têm a mesma "assinatura" que designamos como* -> *
(semelhante à assinatura da função Haskell), porque se você lhes der um tipo concreto, eles cuspirão um tipo concreto. Chamamos isso de "tipo" do tipoMaybe
eeList
temos o mesmo tipo.Diz-se que os tipos de concreto são gentis
*
, e nosso exemplo de mapa é gentil* -> * -> *
porque leva dois tipos concretos como entrada antes que possa gerar um tipo concreto.Você pode ver que é basicamente apenas o número de "parâmetros" que transmitimos ao construtor de tipos - mas percebemos que também podemos obter construtores de tipos aninhados dentro de construtores de tipos, para que possamos terminar com um tipo que se parece,
* -> (* -> *) -> *
por exemplo .Se você é um desenvolvedor do Scala / Java, também pode achar útil esta explicação: https://www.atlassian.com/blog/archives/scala-types-of-a-higher-kind
fonte
Maybe a
um sinônimo deforall a. Maybe a
tipo polimórfico*
eMaybe
um tipo monomórfico* -> *
.