Qual é a diferença entre um tipo e um tipo?


26

Estou aprendendo o idioma Haskell, e estou tentando entender qual é a diferença entre a typee a kind.

Pelo que entendi a kind is a type of type. Por exemplo, a ford is a type of care 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 vehicleenquanto, ao mesmo tempo a car is a **kind** of vehicle. Ou seja, os termos typee kindsão intercambiáveis.

Alguém poderia lançar alguma luz sobre isso?


6
Eu vim aqui do post no Stack Overflow que levou a essa discussão. Não tenho certeza de que estou qualificado para responder em detalhes - mas você definitivamente está sendo muito literal sobre os termos "tipo" e "tipo", tentando relacioná-los ao seu significado em inglês (onde de fato são sinônimos ) Você deve tratá-los como termos técnicos. "Tipo" é bem compreendido por todos os programadores, suponho, porque o conceito é vital para todas as linguagens, mesmo as fracamente tipadas como Javascript, "Tipo" é um termo técnico usado em Haskell para o "tipo de um tipo". Isso é realmente tudo o que existe.
Robin Zigmond

2
@RobinZigmond: você está certo em termos técnicos, mas eles são usados ​​mais amplamente do que apenas em Haskell. Talvez o backlink para a discussão Stack Overflow que gerou essa pergunta?
Andrej Bauer

@AndrejBauer Eu nunca disse que eles não eram usados ​​fora de Haskell, certamente o "tipo" é usado em praticamente todos os idiomas, como eu disse. Na verdade, nunca me deparei com "tipo" fora de Haskell, mas então Haskell é a única linguagem funcional que conheço e tomei cuidado para não dizer que o termo não seja usado em outro lugar, apenas que seja usado dessa maneira em Haskell. (E o link, como você pede, está aqui )
Robin Zigmond

Os idiomas da família ML também têm tipos, por exemplo, ML padrão e OCaml. Eles não são expostos explicitamente por esse nome, eu acho. Eles se manifestam como assinaturas e seus elementos são chamados de estruturas .
Andrej Bauer

11
Uma analogia inglesa mais precisa é que Ford é um tipo de carro e carro é um tipo de veículo, mas ambos os tipos e tipos de veículos são do mesmo tipo: substantivos. Enquanto o vermelho é um tipo de cor do carro e o RPM é um tipo de métrica de desempenho do carro e ambos são do mesmo tipo: adjetivos.
slebetman 6/07

Respostas:


32

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 ae bsempre serão valores do tipo Doublee não, digamos, Stringe Boolrespectivamente. 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é faplicada 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 -> afor válida, que tipo de tipos devemos permitir que a variável aseja? Bem, as expressões de tipo:

Int -> Int
Bool -> Bool

parecem válidos, portanto os tipos Int e Boolsã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 idfunções, até:

(a -> a) -> (a -> a)

Parece bem. Assim, Int, Bool, [Double], Maybe [(Double,Int)], e a -> atodo 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 Maybeseja 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 Doubleou 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 atem tipo * (o tipo de tipos de concreto), o resultado da expressão tipo a -> airá também tem tipo * (ou seja, o tipo de tipos de concreto).

Então, que tipo de tipo é Maybe? Bem, Maybepode ser aplicado a um tipo de concreto para produzir outro tipo de concreto. Assim, Maybeparece 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 Maybeuma 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 ftem tipo * -> *e variáveis ae btem 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 ae f btambé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 bpara 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.


Se "tipos e tipos são realmente a mesma coisa", então o tipo de typeé apenas typeele mesmo, e não haveria necessidade disso kind. Então, qual é exatamente a distinção?
user21820 3/07

11
@ user21820, adicionei uma nota no final que pode resolver isso. Resposta curta: não existe realmente uma distinção no GHC Haskell moderno .
KA Buhr

11
Esta é uma ótima resposta - muito obrigado por compartilhar. É bem escrito e introduz conceitos gradualmente - como alguém que não escreve Haskell há alguns anos, isso é muito apreciado!
ultrafez 03/07

@KABuhr: Obrigado por esse pouco mais!
user21820 4/07

19

type:kEund=set:ceuumass.

  • Bool é um tipo
  • Type é um tipo porque seus elementos são tipos
  • Bool -> Int é um tipo
  • Bool -> Type é um tipo porque seus elementos são funções que retornam tipos
  • Bool * Int é um tipo
  • Bool * Type é um tipo porque seus elementos são pares com um componente por tipo

você0 0você1 1você2você0 0BooeuNumatNumatNumatvocê1 1você0 0Booeuvocê0 0você0 0você0 0vocên+1 1vocênvocên×

você0 0você1 1você0 0você1 1você0 0**U_1


2
Eu não acho que (GHC) Haskell tenha qualquer conceito de universo. Type :: Typeé um axioma. A distinção entre "tipo" e "tipo" é inteiramente na linguagem humana, nesse caso. Truetem um tipo,, Boole Booltem um tipo Type, que por si só tem tipo Type. À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.
HTNW 03/07

11
A distinção não é apenas "linguagem humana", é uma distinção formal no sistema de tipos subjacente. É bem possível ter os dois Type :: Typee uma distinção entre tipos e tipos. Além disso, que parte do código demonstra Type :: Typeem Haskell?
Andrej Bauer

11
Eu também devo dizer que *em Haskell há um tipo de universo. Eles simplesmente não chamam assim.
Andrej Bauer

3
@AndrejBauer Typede Data.Kindse *deve ser sinônimo. Inicialmente, tínhamos apenas *como primitivo, enquanto atualmente é definido internamente como GHC.Types.Typeno módulo interno GHC.Types, por sua vez definido como type Type = TYPE LiftedRep. Eu acho que TYPEé 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.
chi

11
Vou tentar resumir. Se vé um valor, então ele tem um tipo: v :: T. Se Té um tipo, então ele tem um tipo: T :: K. O tipo de tipo é chamado de tipo. Tipos que se parecem TYPE reppodem ser chamados de tipos, embora a palavra seja incomum. Iff T :: TYPE repé Tpermitido aparecer no RHS de a ::. A palavra "tipo" tem nuances: Kin T :: Ké uma espécie, mas não é v :: K, embora seja a mesma K. 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".
HTNW 03/07

5

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, um Int; 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 de Int.)

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, Inttem tipo *, e nenhum outro tipo. MaybeTem tipo * -> *e não há outros tipos. Mas a intuição ainda deve se manter: 42é um Int, e você pode fazer Intcoisas com ele como adicionar e subtrair. em Intsi não é um Int; não existe um número como Int + Int. Você pode ouvir informalmente as pessoas dizerem que Inté a Num, pelo que elas significam que há uma instância da Numclasse de tipo para o tipo Int- isso não é a mesma coisa como dizendo que Inttem tipo Num . Inttem 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 "a Maybe". Em vez disso, podemos ter a Maybe Intou a Maybe 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.


11
Não estou dizendo que sua analogia esteja errada, mas acho que pode causar confusão. Dijkstra tem algumas palavras sobre analogias. Google "Sobre a crueldade de realmente ensinar ciência da computação".
Rafael Castro

Quero dizer, existem analogias de carros e depois existem analogias de carros. Eu não acho que destacar a estrutura implícita de tipos em uma linguagem natural (que, na verdade, eu estiquei na segunda metade) como uma maneira de explicar um sistema formal de tipos seja o mesmo tipo de ensino por analogia que falar sobre o que um programa "quer" fazer.
user11228628 2/07

1

Pelo que entendi, um tipo é um tipo de tipo.

Isso mesmo - então vamos explorar o que isso significa. Intou Textsão tipos concretos, mas Maybe aé um tipo abstrato . Não se tornará um tipo concreto até que você decida qual valor específico deseja apara uma determinada variável (ou valor / expressão / qualquer que seja), por exemplo Maybe Text.

Dizemos que Maybe aé um construtor de tipos porque é como uma função que pega um único tipo de concreto (por exemplo Text) e retorna um tipo de concreto ( Maybe Textneste caso). Mas outros construtores de tipos podem usar ainda mais "parâmetros de entrada" antes de retornar um tipo concreto. por exemplo, Map k vprecisa usar dois tipos de concreto (por exemplo, Inte Text) antes de poder construir um tipo de concreto ( Map Int Text).

Portanto, os construtores de tipo Maybe ae List atê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 tipo Maybeee Listtemos 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


Isso não está correto. Em Haskell, distinguimos entre Maybe aum sinônimo de forall a. Maybe atipo polimórfico *e Maybeum tipo monomórfico * -> *.
b0fh 5/07
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.