J'apprends Haskell de Learnyouahaskell.com . J'ai du mal à comprendre les constructeurs de types et les constructeurs de données. Par exemple, je ne comprends pas vraiment la différence entre ceci:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
et ça:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
Je comprends que la première consiste simplement à utiliser un constructeur ( Car
) pour créer des données de type Car
. Je ne comprends pas vraiment le second.
Aussi, comment les types de données définis comme ceci:
data Color = Blue | Green | Red
s'intégrer dans tout cela?
D'après ce que je comprends, le troisième exemple ( Color
) est un type qui peut être dans trois états: Blue
, Green
ou Red
. Mais cela entre en conflit avec la façon dont je comprends les deux premiers exemples: est-ce que le type Car
ne peut être que dans un seul état Car
, qui peut prendre divers paramètres pour se construire? Si oui, comment le deuxième exemple s'intègre-t-il?
Essentiellement, je cherche une explication qui unifie les trois exemples / constructions de code ci-dessus.
Car
s'agit à la fois d'un constructeur de type (sur le côté gauche du=
) et d'un constructeur de données (sur le côté droit). Dans le premier exemple, leCar
constructeur de type n'accepte aucun argument, dans le second, il en prend trois. Dans les deux exemples, leCar
constructeur de données prend trois arguments (mais les types de ces arguments sont dans un cas fixes et dans l'autre paramétrés).Car :: String -> String -> Int -> Car
) pour créer des données de typeCar
. la seconde consiste simplement à utiliser un constructeur de données (Car :: a -> b -> c -> Car a b c
) pour créer des données de typeCar a b c
.Réponses:
Dans une
data
déclaration, un constructeur de type est la chose sur le côté gauche du signe égal. Le (s) constructeur (s) de données sont les éléments du côté droit du signe égal. Vous utilisez des constructeurs de type là où un type est attendu et vous utilisez des constructeurs de données là où une valeur est attendue.Constructeurs de données
Pour simplifier les choses, nous pouvons commencer par un exemple de type qui représente une couleur.
Ici, nous avons trois constructeurs de données.
Colour
est un type etGreen
est un constructeur qui contient une valeur de typeColour
. De même,Red
etBlue
sont tous deux des constructeurs qui construisent des valeurs de typeColour
. Nous pourrions imaginer le pimenter cependant!Nous n'avons toujours que le type
Colour
, mais ceRGB
n'est pas une valeur - c'est une fonction prenant trois Ints et renvoyant une valeur!RGB
a le typeRGB
est un constructeur de données qui est une fonction prenant certaines valeurs comme arguments, puis les utilise pour construire une nouvelle valeur. Si vous avez effectué une programmation orientée objet, vous devez le reconnaître. En POO, les constructeurs prennent également certaines valeurs comme arguments et renvoient une nouvelle valeur!Dans ce cas, si nous appliquons
RGB
à trois valeurs, nous obtenons une valeur de couleur!Nous avons construit une valeur de type
Colour
en appliquant le constructeur de données. Un constructeur de données contient une valeur comme le ferait une variable ou prend d'autres valeurs comme argument et crée une nouvelle valeur . Si vous avez déjà fait de la programmation, ce concept ne devrait pas vous être très étrange.Entracte
Si vous voulez construire un arbre binaire pour stocker
String
s, vous pouvez imaginer faire quelque chose commeCe que nous voyons ici est un type
SBTree
qui contient deux constructeurs de données. En d'autres termes, il existe deux fonctions (à savoirLeaf
etBranch
) qui construiront des valeurs deSBTree
type. Si vous n'êtes pas familier avec le fonctionnement des arbres binaires, accrochez-vous simplement. Vous n'avez pas vraiment besoin de savoir comment fonctionnent les arbres binaires, seulement que celui-ci stocke lesString
s d'une manière ou d'une autre.Nous voyons également que les deux constructeurs de données prennent un
String
argument - c'est la chaîne qu'ils vont stocker dans l'arborescence.Mais! Et si nous voulions également pouvoir stocker
Bool
, nous devions créer un nouvel arbre binaire. Cela pourrait ressembler à ceci:Constructeurs de type
Les deux
SBTree
etBBTree
sont des constructeurs de type. Mais il y a un problème flagrant. Voyez-vous à quel point ils sont similaires? C'est le signe que vous voulez vraiment un paramètre quelque part.Nous pouvons donc faire ceci:
Maintenant, nous introduisons une variable de type en
a
tant que paramètre du constructeur de type. Dans cette déclaration,BTree
est devenue une fonction. Il prend un type comme argument et renvoie un nouveau type .Si nous passons, disons,
Bool
comme argument àBTree
, il renvoie le typeBTree Bool
, qui est un arbre binaire qui stockeBool
s. Remplacez chaque occurrence de la variable de typea
par le typeBool
, et vous pourrez voir par vous-même comment c'est vrai.Si vous le souhaitez, vous pouvez voir
BTree
comme une fonction avec le genreLes types sont un peu comme des types - le
*
indique un type concret, donc nous disons qu'ilBTree
va d'un type concret à un type concret.Emballer
Revenez ici un moment et prenez note des similitudes.
Un constructeur de données est une «fonction» qui prend 0 ou plusieurs valeurs et vous renvoie une nouvelle valeur.
Un constructeur de type est une "fonction" qui prend 0 ou plusieurs types et vous redonne un nouveau type.
Les constructeurs de données avec des paramètres sont sympas si nous voulons de légères variations dans nos valeurs - nous mettons ces variations dans les paramètres et laissons le créateur de la valeur décider quels arguments ils vont mettre. Dans le même sens, les constructeurs de types avec des paramètres sont cool si nous voulons de légères variations dans nos types! Nous mettons ces variations comme paramètres et laissons le type qui crée le type décider des arguments qu'il va mettre.
Une étude de cas
Comme la dernière ligne droite ici, nous pouvons considérer le
Maybe a
type. Sa définition estVoici
Maybe
un constructeur de type qui renvoie un type concret.Just
est un constructeur de données qui renvoie une valeur.Nothing
est un constructeur de données qui contient une valeur. Si nous regardons le type deJust
, nous voyons queEn d'autres termes,
Just
prend une valeur de typea
et renvoie une valeur de typeMaybe a
. Si nous regardons le genre deMaybe
, nous voyons queEn d'autres termes,
Maybe
prend un type concret et renvoie un type concret.Encore une fois! La différence entre un type concret et une fonction constructeur de type. Vous ne pouvez pas créer une liste de
Maybe
s - si vous essayez d'exécutervous obtiendrez une erreur. Vous pouvez cependant créer une liste de
Maybe Int
, ouMaybe a
. C'est parce queMaybe
c'est une fonction de constructeur de type, mais une liste doit contenir des valeurs d'un type concret.Maybe Int
etMaybe a
sont des types concrets (ou si vous le souhaitez, des appels à des fonctions de constructeur de type qui renvoient des types concrets.)la source
data Colour = Red | Green | Blue
"nous n'avons aucun constructeur du tout" est totalement erronée. Les constructeurs de types et les constructeurs de données n'ont pas besoin de prendre d'arguments, voir par exemple haskell.org/haskellwiki/Constructor qui souligne que dansdata Tree a = Tip | Node a (Tree a) (Tree a)
«il y a deux constructeurs de données, Tip et Node».-XEmptyDataDecls
) qui vous permet de le faire. Comme, comme vous le dites, il n'y a pas de valeurs avec ce type, une fonctionf :: Int -> Z
peut par exemple ne jamais retourner (car que retournerait-elle?) Elles peuvent cependant être utiles lorsque vous voulez des types mais que vous ne vous souciez pas vraiment des valeurs .:k Z
et ça m'a juste donné une étoile.Haskell a des types de données algébriques , que très peu d'autres langages ont. C'est peut-être ce qui vous déroute.
Dans d'autres langues, vous pouvez généralement créer un "enregistrement", une "structure" ou similaire, qui contient un tas de champs nommés contenant différents types de données. Vous pouvez aussi parfois faire une "énumération", qui a un (petit) ensemble de valeurs fixes possibles (par exemple, votre
Red
,Green
etBlue
).Dans Haskell, vous pouvez combiner les deux en même temps. Bizarre, mais vrai!
Pourquoi est-il appelé «algébrique»? Eh bien, les nerds parlent de «types de somme» et de «types de produits». Par exemple:
Une
Eg1
valeur est essentiellement soit un nombre entier ou une chaîne. Ainsi, l'ensemble de toutes lesEg1
valeurs possibles est la «somme» de l'ensemble de toutes les valeurs entières possibles et de toutes les valeurs de chaîne possibles. Ainsi, les nerds se réfèrent àEg1
un "type de somme". D'autre part:Chaque
Eg2
valeur se compose à la fois d' un entier et d'une chaîne. Ainsi, l'ensemble de toutes lesEg2
valeurs possibles est le produit cartésien de l'ensemble de tous les entiers et de l'ensemble de toutes les chaînes. Les deux ensembles sont "multipliés" ensemble, il s'agit donc d'un "type de produit".Les types algébriques de Haskell sont des types de somme de types de produits . Vous donnez à un constructeur plusieurs champs pour créer un type de produit, et vous avez plusieurs constructeurs pour faire une somme (de produits).
Pour illustrer pourquoi cela pourrait être utile, supposons que vous ayez quelque chose qui génère des données au format XML ou JSON, et qu'il prend un enregistrement de configuration - mais évidemment, les paramètres de configuration pour XML et pour JSON sont totalement différents. Vous pourriez donc faire quelque chose comme ceci:
(Avec quelques champs appropriés, évidemment.) Vous ne pouvez pas faire de choses comme ça dans les langages de programmation normaux, c'est pourquoi la plupart des gens n'y sont pas habitués.
la source
union
s, avec une discipline de tag. :)union
, les gens me regardent comme "qui diable utilise jamais ça ??" ;-)union
utilisé dans ma carrière C. Veuillez ne pas le rendre inutile car ce n'est pas le cas.Commencez par le cas le plus simple:
Ceci définit un "constructeur de type"
Color
qui ne prend aucun argument - et il a trois "constructeurs de données"Blue
,Green
etRed
. Aucun des constructeurs de données ne prend d'argument. Cela signifie que il y a trois de typeColor
:Blue
,Green
etRed
.Un constructeur de données est utilisé lorsque vous devez créer une valeur quelconque. Comme:
crée une valeur
myFavoriteColor
à l'aide duGreen
constructeur de données - etmyFavoriteColor
sera de typeColor
puisque c'est le type de valeurs produites par le constructeur de données.Un constructeur de type est utilisé lorsque vous devez créer un type quelconque. C'est généralement le cas lors de l'écriture de signatures:
Dans ce cas, vous appelez le
Color
constructeur de type (qui ne prend aucun argument).Encore avec moi?
Maintenant, imaginez que vous vouliez non seulement créer des valeurs rouge / vert / bleu, mais que vous vouliez également spécifier une "intensité". Comme, une valeur comprise entre 0 et 256. Vous pouvez le faire en ajoutant un argument à chacun des constructeurs de données, de sorte que vous vous retrouvez avec:
Maintenant, chacun des trois constructeurs de données prend un argument de type
Int
. Le constructeur de type (Color
) ne prend toujours aucun argument. Donc, ma couleur préférée étant un vert foncé, je pourrais écrireEt encore une fois, il appelle le
Green
constructeur de données et j'obtiens une valeur de typeColor
.Imaginez si vous ne voulez pas dicter la manière dont les gens expriment l'intensité d'une couleur. Certains voudront peut-être une valeur numérique comme nous venons de le faire. D'autres peuvent convenir avec juste un booléen indiquant "brillant" ou "pas si brillant". La solution à cela est de ne pas coder
Int
en dur dans les constructeurs de données, mais plutôt d'utiliser une variable de type:Maintenant, notre constructeur de type prend un argument (un autre type que nous venons d'appeler
a
!) Et tous les constructeurs de données prendront un argument (une valeur!) De ce typea
. Alors tu pourrais avoirou
Remarquez comment nous appelons le
Color
constructeur de type avec un argument (un autre type) pour obtenir le type "effectif" qui sera retourné par les constructeurs de données. Cela touche au concept des types que vous voudrez peut-être lire autour d'une tasse de café ou deux.Nous avons maintenant compris ce que sont les constructeurs de données et les constructeurs de types, et comment les constructeurs de données peuvent prendre d'autres valeurs comme arguments et les constructeurs de types peuvent prendre d'autres types comme arguments. HTH.
la source
->
dans la signature.a
indata Color a = Red a
.a
est un espace réservé pour un type arbitraire. Vous pouvez avoir la même chose dans les fonctions simples, par exemple, une fonction de type(a, b) -> a
prend un tuple de deux valeurs (de typesa
etb
) et donne la première valeur. C'est une fonction «générique» en ce qu'elle ne dicte pas le type des éléments de tuple - elle spécifie seulement que la fonction renvoie une valeur du même type que le premier élément de tuple.Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.
Ceci est très utile.Comme d'autres l'ont souligné, le polymorphisme n'est pas si terrible que cela utile ici. Regardons un autre exemple que vous connaissez probablement déjà:
Ce type a deux constructeurs de données.
Nothing
est un peu ennuyeux, il ne contient aucune donnée utile. D'autre part, ilJust
contient une valeur dea
- quel que soit le typea
. Écrivons une fonction qui utilise ce type, par exemple obtenir la tête d'uneInt
liste, s'il y en a (j'espère que vous êtes d'accord que c'est plus utile que de lancer une erreur):Donc, dans ce cas,
a
c'est unInt
, mais cela fonctionnerait aussi bien pour tout autre type. En fait, vous pouvez faire fonctionner notre fonction pour chaque type de liste (même sans changer l'implémentation):D'autre part, vous pouvez écrire des fonctions qui n'acceptent qu'un certain type de
Maybe
, par exempleBref, avec le polymorphisme, vous donnez à votre propre type la flexibilité de travailler avec des valeurs de différents autres types.
Dans votre exemple, vous pouvez décider à un moment donné que ce
String
n'est pas suffisant pour identifier l'entreprise, mais elle doit avoir son propre typeCompany
(qui contient des données supplémentaires telles que le pays, l'adresse, les comptes en arrière, etc.). Votre première implémentation deCar
devrait changer pour utiliserCompany
au lieu deString
pour sa première valeur. Votre deuxième implémentation est très bien, vous l'utilisez commeCar Company String Int
et cela fonctionnerait comme avant (bien sûr, les fonctions accédant aux données de l'entreprise doivent être modifiées).la source
data Color = Blue ; data Bright = Color
? Je l'ai essayé dans ghci, et il semble que la couleur dans le constructeur de type n'a rien à voir avec le constructeur de données Color dans la définition Bright. Il n'y a que 2 constructeurs Color, l'un qui est Data et l'autre Type.data
ounewtype
(par exempledata Bright = Bright Color
), ou vous pouvez utilisertype
pour définir un synonyme (par exempletype Bright = Color
).Le second contient la notion de «polymorphisme».
Le
a b c
peut être de n'importe quel type. Par exemple,a
peut être un[String]
,b
peut être[Int]
etc
peut être[Char]
.Alors que le premier type est fixe: la société est un
String
, le modèle est unString
et l'année estInt
.L'exemple de Car pourrait ne pas montrer l'importance de l'utilisation du polymorphisme. Mais imaginez que vos données soient du type liste. Une liste peut contenir
String, Char, Int ...
Dans ces situations, vous aurez besoin de la deuxième façon de définir vos données.Quant à la troisième façon, je ne pense pas qu'elle doive s'inscrire dans le type précédent. C'est juste une autre façon de définir les données dans Haskell.
C'est mon humble avis en tant que débutant moi-même.
Btw: Assurez-vous que vous entraînez bien votre cerveau et que vous vous sentez à l'aise pour cela. C'est la clé pour comprendre Monad plus tard.
la source
Il s'agit de types : dans le premier cas, vous définissez les types
String
(pour la société et le modèle) etInt
pour l'année. Dans le second cas, vous êtes plus générique.a
,b
etc
peuvent être du même type que dans le premier exemple, ou quelque chose de complètement différent. Par exemple, il peut être utile de donner l'année sous forme de chaîne au lieu d'un entier. Et si vous le souhaitez, vous pouvez même utiliser votreColor
type.la source