Em uma datadeclaração, um construtor de tipo é a coisa do lado esquerdo do sinal de igual. O (s) construtor (es) de dados são os itens do lado direito do sinal de igual. Você usa construtores de tipo onde um tipo é esperado e usa construtores de dados onde um valor é esperado.
Construtores de dados
Para simplificar, podemos começar com um exemplo de um tipo que representa uma cor.
data Colour = Red | Green | Blue
Aqui, temos três construtores de dados. Colouré um tipo, e Greené um construtor que contém um valor de tipo Colour. Da mesma forma, Rede Bluesão ambos construtores que constroem valores do tipo Colour. Poderíamos imaginar apimentá-lo embora!
data Colour = RGB Int Int Int
Ainda temos apenas o tipo Colour, mas RGBnão é um valor - é uma função que leva três Ints e retorna um valor! RGBtem o tipo
RGB :: Int -> Int -> Int -> Colour
RGBé um construtor de dados que é uma função que aceita alguns valores como argumentos e os usa para construir um novo valor. Se você fez alguma programação orientada a objetos, deve reconhecer isso. No OOP, os construtores também recebem alguns valores como argumentos e retornam um novo valor!
Nesse caso, se aplicarmos RGBa três valores, obteremos um valor de cor!
Prelude> RGB 12 92 27
#0c5c1b
Temos construído um valor do tipo Colouraplicando o construtor de dados. Um construtor de dados contém um valor como uma variável ou aceita outros valores como argumento e cria um novo valor . Se você já fez a programação anterior, esse conceito não deve ser muito estranho para você.
Intervalo
Se você deseja construir uma árvore binária para armazenar Strings, você pode imaginar fazer algo como
data SBTree = Leaf String
| Branch String SBTree SBTree
O que vemos aqui é um tipo SBTreeque contém dois construtores de dados. Em outras palavras, existem duas funções (a saber Leafe Branch) que construirão valores do SBTreetipo. Se você não estiver familiarizado com o funcionamento das árvores binárias, aguarde um pouco. Na verdade, você não precisa saber como as árvores binárias funcionam, apenas que essa armazena Strings de alguma maneira.
Também vemos que os dois construtores de dados usam um Stringargumento - essa é a String que eles armazenam na árvore.
Mas! E se nós também quiséssemos armazenar Bool, teríamos que criar uma nova árvore binária. Pode ser algo como isto:
data BBTree = Leaf Bool
| Branch Bool BBTree BBTree
Tipo construtores
Ambos SBTreee BBTreesão construtores de tipo. Mas há um problema evidente. Você vê como eles são semelhantes? Isso é um sinal de que você realmente deseja um parâmetro em algum lugar.
Para que possamos fazer isso:
data BTree a = Leaf a
| Branch a (BTree a) (BTree a)
Agora, introduzimos uma variável de tipo a como parâmetro no construtor de tipos. Nesta declaração, BTreetornou-se uma função. Ele aceita um tipo como argumento e retorna um novo tipo .
É importante considerar aqui a diferença entre um tipo concreto (exemplos incluem Int, [Char]e Maybe Bool), que é um tipo que pode ser atribuído a um valor em seu programa e uma função construtora de tipo que você precisa alimentar um tipo para poder ser atribuído a um valor. Um valor nunca pode ser do tipo "lista", porque precisa ser uma "lista de algo ". No mesmo espírito, um valor nunca pode ser do tipo "árvore binária", porque precisa ser uma "árvore binária que armazena algo ".
Se passarmos, digamos, Boolcomo argumento para BTree, ele retornará o tipo BTree Bool, que é uma árvore binária que armazena Bools. Substitua todas as ocorrências da variável type apelo tipo Boole você pode ver por si mesmo como é verdade.
Se desejar, você pode visualizar BTreecomo uma função do tipo
BTree :: * -> *
Tipos são semelhantes aos tipos - *indica um tipo concreto, por isso dizemos que BTreeé de um tipo concreto para um tipo concreto.
Empacotando
Volte aqui um momento e tome nota das semelhanças.
Construtores de dados com parâmetros são legais se queremos pequenas variações em nossos valores - colocamos essas variações nos parâmetros e deixamos o cara que cria o valor decidir em quais argumentos eles serão inseridos. No mesmo sentido, digite construtores com parâmetros como legais se queremos pequenas variações em nossos tipos! Colocamos essas variações como parâmetros e deixamos o cara que cria o tipo decidir em quais argumentos eles irão colocar.
Um estudo de caso
Como o trecho da casa aqui, podemos considerar o Maybe atipo. Sua definição é
data Maybe a = Nothing
| Just a
Aqui Maybeestá um construtor de tipos que retorna um tipo concreto. Justé um construtor de dados que retorna um valor. Nothingé um construtor de dados que contém um valor. Se olharmos para o tipo de Just, vemos que
Just :: a -> Maybe a
Em outras palavras, Justpega um valor do tipo ae retorna um valor do tipo Maybe a. Se olharmos para o tipo de coisa Maybe, vemos que
Maybe :: * -> *
Em outras palavras, Maybepega um tipo concreto e retorna um tipo concreto.
De novo! A diferença entre um tipo concreto e uma função construtora de tipo. Você não pode criar uma lista de Maybes - se tentar executar
[] :: [Maybe]
você receberá um erro. No entanto, você pode criar uma lista de Maybe Int, ou Maybe a. Isso Maybeocorre porque é uma função construtora de tipo, mas uma lista precisa conter valores de um tipo concreto. Maybe Inte Maybe asão tipos concretos (ou, se desejar, chamadas para digitar funções construtoras que retornam tipos concretos.)
Caré um construtor de tipos (no lado esquerdo da=) e um construtor de dados (no lado direito). No primeiro exemplo, oCarconstrutor de tipos não usa argumentos, no segundo exemplo, três. Nos dois exemplos, oCarconstrutor de dados usa três argumentos (mas os tipos desses argumentos são corrigidos em um caso e no outro parametrizados).