Qu'est-ce que le mot-clé `forall` dans Haskell / GHC faire?
Question
Je commence à comprendre comment le mot-clé forall
est utilisé dans ce qu'on appelle « types existentiels » comme ceci:
data ShowBox = forall s. Show s => SB s
Ceci est un sous-ensemble, cependant, de la façon dont forall
est utilisé et je ne peux tout simplement pas mon esprit sur son utilisation dans des choses comme ceci:
runST :: forall a. (forall s. ST s a) -> a
Ou expliquer pourquoi ceux-ci sont différentes:
foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))
Ou l'ensemble des trucs RankNTypes
...
J'ai tendance à préférer clair, sans jargon anglais plutôt que les types de langage qui sont normaux dans les milieux universitaires. La plupart des explications je tente de lire sur ce (ceux que je peux trouver par les moteurs de recherche) ont ces problèmes:
- Ils sont incomplètes. Ils expliquent une partie de l'utilisation de ce mot-clé (comme « types existentiels ») qui me fait me sentir heureux jusqu'à ce que je lis le code qui l'utilise d'une manière complètement différente (comme
runST
,foo
etbar
ci-dessus). - Ils sont densément emballé avec des hypothèses que j'ai lu la dernière de quelque branche de mathématiques discrètes, théorie des catégories ou l'algèbre abstraite est populaire cette semaine. (Si je ne lis jamais les mots « consulter le journal tout pour les détails de la mise en œuvre » à nouveau, il sera trop tôt.)
- Ils sont écrits d'une manière qui se tournent fréquemment les concepts même simples dans la grammaire et la sémantique tordue tortueuse et fracturée.
...
Sur la question réelle. Quelqu'un peut-il expliquer complètement le mot-clé forall
en anglais clair et simple (ou, si elle existe quelque part, pointez sur une telle explication claire que je l'ai manqué) qui ne suppose pas que je suis un mathématicien imprégné dans le jargon?
Edité à ajouter:
Il y avait deux stand-out des réponses de ceux de qualité supérieure ci-dessous, mais malheureusement je ne peux en choisir un comme meilleur. de réponse de Norman a été détaillé et utile , ce qui explique les choses d'une manière qui a montré quelques-uns des fondements théoriques de forall
et en même temps me montrant quelques-unes des implications pratiques de celui-ci. réponse de yairchu couvrait une personne zone (autre mentionné les variables de type scope) et illustré tous les concepts avec code et une session GHCi. Se il était possible de sélectionner à la fois mieux, je le ferais. Malheureusement, je ne peux pas et, après avoir examiné les réponses de près, je l'ai décidé que yairchu dépasse légèrement ya Norman en raison du code d'illustration et l'explication ci-joint. Ceci est un peu injuste, mais, parce que vraiment je besoin à la fois des réponses à comprendre au point que forall
ne me laisse pas un vague sentiment d'effroi quand je le vois dans une signature de type.
La solution
Commençons Let avec un exemple de code:
foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
foob postProcess onNothin onJust mval =
postProcess val
where
val :: b
val = maybe onNothin onJust mval
Ce code ne compile pas (erreur de syntaxe) dans Haskell ordinaire 98. Il faut une extension pour soutenir le mot-clé forall
.
En fait, il y a 3 différentes utilisations communes pour le mot-clé forall
(ou au moins il semble ), et chacun a sa propre extension Haskell: ScopedTypeVariables
, RankNTypes
/ Rank2Types
, ExistentialQuantification
.
Le code ci-dessus ne reçoit pas une erreur de syntaxe avec l'un de ces permis, mais seulement le type de contrôles avec ScopedTypeVariables
activé.
Scoped variables de type:
variables de type Scoped aide un spécifier les types de code à l'intérieur des clauses de where
. Il fait la b
dans val :: b
le même que le b
dans foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
.
Un point confus : vous pouvez entendre que lorsque vous omettez le forall
d'un type, il est en réalité toujours là implicitement. ( de la réponse de Norman: « normalement ces langues omettent le forall de types polymorphes "). Cette affirmation est correcte, mais il se réfère aux autres utilisations de forall
, et non à l'utilisation de ScopedTypeVariables
.
Classement-N-types:
Soit Commençons avec cette mayb :: b -> (a -> b) -> Maybe a -> b
équivaut à mayb :: forall a b. b -> (a -> b) -> Maybe a -> b
, sauf lorsque ScopedTypeVariables
est activé.
Cela signifie que cela fonctionne pour tous les a
et b
.
Le mot Let que vous voulez faire quelque chose comme ça.
ghci> let putInList x = [x]
ghci> liftTup putInList (5, "Blah")
([5], ["Blah"])
Quel doit être le type de cette liftTup
? Il est liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
. Pour voir pourquoi, nous allons essayer de code, il:
ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
ghci> liftTup (\x -> [x]) (5, "Hello")
No instance for (Num [Char])
...
ghci> -- huh?
ghci> :t liftTup
liftTup :: (t -> t1) -> (t, t) -> (t1, t1)
« Hmm .. pourquoi GHC déduisons que le tuple doit contenir deux du même type? Raconte-moi ce qu'ils ne doivent pas être »
-- test.hs
liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)
ghci> :l test.hs
Couldnt match expected type 'x' against inferred type 'b'
...
Hmm. donc ici GHC ne nous laisse pas appliquer liftFunc
sur v
parce v :: b
et liftFunc
veut un x
. Nous voulons vraiment notre fonction pour obtenir une fonction qui accepte toute x
possible!
{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)
Il est liftTup
pas que des œuvres pour tous x
, c'est la fonction qu'il obtient qui.
Existentielle Quantification:
L'utilisation Soit un exemple:
-- test.hs
{-# LANGUAGE ExistentialQuantification #-}
data EQList = forall a. EQList [a]
eqListLen :: EQList -> Int
eqListLen (EQList x) = length x
ghci> :l test.hs
ghci> eqListLen $ EQList ["Hello", "World"]
2
Comment est-ce différent de Rang-N-types?
ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
Couldnt match expected type 'a' against inferred type '[Char]'
...
Rang-N-types, forall a
signifie que votre expression doit correspondre à tous les a
s possibles. Par exemple:
ghci> length ([] :: forall a. [a])
0
Une liste vide fonctionne comme une liste de tout type.
avec Existentielle-Quantification, forall
s dans les définitions de data
signifient que la valeur contenue peut être tout type approprié, pas qu'il doit être de tous les types appropriés .
Autres conseils
Quelqu'un peut-il complètement expliquer le mot-clé forall en anglais clair et simple?
Non. (Eh bien, peut-être Don Stewart peut.)
Voici les obstacles à une simple explication claire ou forall
:
-
Il est un quantificateur. Vous avez un avoir au moins un peu logique (calcul des prédicats) avoir vu un quantificateur universel ou existentiel. Si vous ne l'avez jamais vu calcul des prédicats ou ne sont pas à l'aise avec quantificateurs (et j'ai vu des élèves au cours des examens de qualification doctorat qui ne sont pas à l'aise), alors pour vous, il n'y a pas d'explication facile de
forall
. -
Il est type quantificateurs. Si vous ne l'avez pas vu et obtenu une certaine pratique d'écriture types polymorphes, vous allez trouver
forall
confusion. L'expérience avec Haskell ou ML ne suffit pas, parce que normalement ces langues omettent leforall
de types polymorphes. (Dans mon esprit, c'est une erreur-conception du langage.) -
Dans Haskell en particulier
forall
est utilisé de façon que je trouve déroutant. (Je ne suis pas un théoricien de type, mais mon travail me met en contact avec beaucoup de la théorie du type, et je suis tout à fait à l'aise.) Pour moi, la principale source de confusion est queforall
est utilisé pour coder un type que je me préféreraient écrire avecexists
. Il est justifié par un peu délicat de type impliquant isomorphisme quantificateurs et des flèches, et chaque fois que je veux comprendre, je dois regarder les choses et le travail moi-même isomorphisme.Si vous n'êtes pas à l'aise avec l'idée de isomorphisme de type, ou si vous n'avez pas la pensée de la pratique sur le type isomorphismes, cette utilisation de
forall
va vous stymie. -
Alors que le concept général de
forall
est toujours le même (obligatoire pour introduire une variable de type), les détails des différentes utilisations peuvent varier considérablement. Informel anglais n'est pas un très bon outil pour expliquer les variations. Pour vraiment comprendre ce qui se passe, vous avez besoin des mathématiques. Dans ce cas, les mathématiques pertinentes se trouvent dans le texte d'introduction de Benjamin Pierce Types et langages de programmation , qui est un très bon livre.
En ce qui concerne vos exemples particuliers,
-
runST
devrait faire votre mal à la tête. types de rang supérieur (forall à gauche d'une flèche) sont rarement trouvés dans la nature. Je vous encourage à lire le journal qui a introduitrunST
: "Lazy Threads fonctionnels État" . Ceci est un très bon papier, et il vous donnera une meilleure intuition pour le type derunST
en particulier et pour les types de rang supérieur en général. L'explication prend plusieurs pages, il est très bien fait, et je ne vais pas essayer de le condenser ici. -
Considérez
foo :: (forall a. a -> a) -> (Char,Bool) bar :: forall a. ((a -> a) -> (Char, Bool))
Si je l'appelle
bar
, je peux simplement choisir tout typea
que je l'aime, et je peux passer une fonction à partir du typea
typea
. Par exemple, je peux passer la fonction(+1)
ou la fonctionreverse
. Vous pouvez penser à laforall
en disant: « Je reçois de choisir le type maintenant ». (Le mot technique pour choisir le type est instanciation ).Les restrictions relatives aux appels
foo
sont beaucoup plus strictes: l'argumentfoo
doit une fonction polymorphique. Avec ce type, les seules fonctions que je peux passer àfoo
sontid
ou une fonction qui diverge toujours ou des erreurs, commeundefined
. La raison est que, avecfoo
, leforall
est à gauche de la flèche, afin de l'appelantfoo
je ne suis pas à choisir cea
est plutôt c'est le la mise en œuvre defoo
qui arrive à choisir ce quea
est. Parce queforall
est à gauche de la flèche, plutôt qu'au-dessus de la flèche comme dansbar
, l'instanciation a lieu dans le corps de la fonction plutôt que sur le site d'appel.
Résumé: complet explication du mot-clé forall
nécessite des mathématiques et peut être comprise que par une personne qui a étudié les mathématiques. Même des explications partielles sont difficiles à comprendre sans les mathématiques. Mais peut-être mes explications partielles, non-mathématiques aident un peu. Allez lire Jones Launchbury et Peyton runST
!
Addendum: Jargon "au-dessus", "en dessous", "à gauche de". Cela n'a rien à voir avec le textuelle façons types sont écrits et tout à voir avec des arbres de syntaxe abstraite. Dans la syntaxe abstraite, un forall
prend le nom d'une variable de type, et puis il y a un type complet « ci-dessous » la forall. Une flèche prend deux types (type d'argument et le résultat) et forme un nouveau type (le type de fonction). Le type d'argument est « à gauche de » la flèche; il est enfant de la flèche gauche dans l'arbre de syntaxe abstraite.
Exemples:
-
forall a . [a] -> [a]
, le forall est au-dessus de la flèche; ce qui est à gauche de la flèche est[a]
. -
Dans
forall n f e x . (forall e x . n e x -> f -> Fact x f) -> Block n e x -> f -> Fact x f
type entre parenthèses serait appelé « un forall à gauche d'une flèche ». (J'utilise des types comme ça dans un optimiseur je travaille.)
Ma réponse originale:
Quelqu'un peut-il expliquer complètement le mot-clé forall en clair, simple anglais
Norman indique, il est très difficile de donner une explication en anglais clair et simple d'un terme technique de la théorie de type. Nous essayons tous bien.
Il n'y a vraiment une chose à retenir sur « forall »: il se lie à des types une certaine portée . Une fois que vous comprenez que, tout est assez facile. C'est le équivalent de « lambda » (ou une forme de « laisser ») au niveau du type - Norman Ramsey utilise la notion de « gauche » / « au-dessus » de transmettre ce même concept de champ dans son excellente réponse.
La plupart des utilisations de « forall » sont très simples, et vous pouvez les trouver introduites dans la GHC Manuel d'utilisation, S7.8 ., En particulier l'excellent S7.8.5 sur imbriquée formes de 'forall'.
Dans Haskell, nous partons généralement hors du liant pour les types, lorsque le type est universellement quanitified, comme suit:
length :: forall a. [a] -> Int
est équivalent à:
length :: [a] -> Int
Voilà.
Comme vous pouvez lier des variables de type maintenant une certaine portée, vous pouvez avoir d'autres champs d'application que le niveau supérieur ( " quantifiés universellement "), comme votre premier exemple, où la variable de type est seulement visible à l'intérieur de la structure de données. Ceci permet pour les types cachés ( " types existentiels "). Ou nous pouvons avoir arbitraire imbrication des liaisons ( "rang n types").
Pour comprendre en profondeur les systèmes de type, vous aurez besoin d'apprendre un peu de jargon. C'est la nature de la science informatique. Cependant, les utilisations simples, comme ci-dessus, devraient être pouvant être saisi de manière intuitive, par analogie avec « let » au niveau de la valeur. UNE excellente introduction est Launchbury et Peyton Jones .
Ils sont densément emballé avec des hypothèses que j'ai lu la dernière de quelque branche de mathématiques discrètes, théorie des catégories ou l'algèbre abstraite est populaire cette semaine. (Si je ne lis jamais les mots « consulter le papier tout pour les détails de la mise en œuvre » à nouveau, il sera trop tôt.)
Er, et qu'en est logique simple de premier ordre? forall
est assez clairement en référence à la quantification universelle , et dans ce contexte, le terme existentiel plus de sens aussi bien, mais il serait moins gênant s'il y avait un mot-clé exists
. Que la quantification est effectivement dépend universel ou existentiel sur la mise en place du quantificateur par rapport à l'endroit où les variables sont utilisées de quel côté d'une flèche de fonction et il est tout un peu confus.
Donc, si cela ne veut pas de l'aide, ou si vous ne tout simplement pas comme la logique symbolique, dans une perspective à la rigueur de programmation plus fonctionnel, vous pouvez penser à des variables de type comme étant juste (implicite) type paramètres à la fonction. Les fonctions sont traditionnellement écrites prenant des paramètres de type dans ce sens à l'aide d'un lambda de capital pour une raison quelconque, que je vais écrire ici /\
.
Alors, considérez la fonction id
:
id :: forall a. a -> a
id x = x
On peut réécrire comme lambdas, en déplaçant le "paramètre de type" de la signature de type et l'ajout d'annotations de type en ligne:
id = /\a -> (\x -> x) :: a -> a
Voici la même chose fait à const
:
const = /\a b -> (\x y -> x) :: a -> b -> a
Ainsi, votre fonction bar
pourrait être quelque chose comme ceci:
bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)
Notez que le type de la fonction donnée à bar
comme argument dépend du paramètre de type de bar
. Demandez-vous si vous aviez quelque chose comme ceci:
bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)
Ici bar2
applique la fonction à quelque chose du type Char
, donnant ainsi bar2
tout type autre paramètre que Char
provoquera une erreur de type.
D'autre part, voici ce que foo
pourrait ressembler à:
foo = (\f -> (f Char 't', f Bool True))
Contrairement à bar
, foo
ne prend pas en fait tout à tous les paramètres de type! Il faut une fonction se prend un paramètre de type, puis applique cette fonction à deux différents types.
Alors, quand vous voyez un forall
dans une signature de type, il suffit de penser comme un expression lambda pour les signatures de type . Tout comme lambdas réguliers, la portée de forall
étend jusqu'à à droite possible, jusqu'à parenthèse enfermer, et tout comme les variables liées dans une lambda régulière, les variables de type lié par un forall
ne sont portée dans l'expression quantifiée.
Post Scriptum : Peut-être que vous pourriez vous demander - maintenant que nous réfléchissons sur les fonctions prenant des paramètres de type, pourquoi ne pouvons-nous faire quelque chose de plus intéressant avec ces paramètres que les mettre dans une signature de type ? La réponse est que nous pouvons!
Une fonction qui met les variables de type avec une étiquette et retourne un nouveau type est un constructeur de type , que vous pouvez écrire quelque chose comme ceci:
Either = /\a b -> ...
Mais nous aurions besoin complètement nouvelle notation, car la façon dont un tel type est écrit, comme Either a b
, est déjà évocateur de « appliquer la fonction Either
à ces paramètres ».
D'autre part, une fonction de ce genre de « matches de motif » sur ses paramètres de type, de retour des valeurs différentes pour différents types, est méthode d'une classe de type . Une légère expansion à ma syntaxe /\
ci-dessus suggère quelque chose comme ceci:
fmap = /\ f a b -> case f of
Maybe -> (\g x -> case x of
Just y -> Just b g y
Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
[] -> (\g x -> case x of
(y:ys) -> g y : fmap [] a b g ys
[] -> [] b) :: (a -> b) -> [a] -> [b]
Personnellement, je pense que je préfère syn réelle de Haskelltaxe ...
Une fonction qui "correspond à motif" ses paramètres de type et renvoie un arbitraire, type existant est un type de famille ou dépendance fonctionnelle - dans le premier cas, il a même semble déjà beaucoup comme une définition de fonction.
Voici une explication rapide et sale en termes clairs que vous êtes susceptible d'être déjà familier.
Le mot-clé est forall
vraiment utilisé que d'une manière à Haskell. Cela signifie toujours la même chose quand vous le voyez.
quantification universelle
Type universellement quantifiés est un type de forme forall a. f a
. Une valeur de ce type peut être considéré comme fonction qui prend un type a
comme argument et retourne une valeur de type f a
. Sauf que dans Haskell ces arguments de type sont passés implicitement par le système de type. Cette « fonction » doit vous donner la même valeur quel que soit le type qu'il reçoit, de sorte que la valeur est polymorphes .
Par exemple, considérons le forall a. [a]
type. Une valeur de ce type prend un autre type a
et vous renvoie une liste d'éléments de ce même type a
. Il n'y a qu'une seule mise en œuvre possible, bien sûr. Il devrait vous donner la liste vide parce a
pourrait être absolument tout type. La liste vide est la seule valeur de la liste qui est polymorphe dans son type d'élément (car elle n'a pas d'éléments).
Ou le forall a. a -> a
type. L'appelant de la fonction d'une telle offre à la fois un type a
et une valeur de type a
. La mise en œuvre doit alors retourner une valeur de ce même type a
. Il n'y a qu'une seule mise en œuvre à nouveau possible. Il devrait retourner la même valeur qu'il a été donné.
quantification existentielle
Une de type existentiellement quantifiés aurait la forme exists a. f a
, si Haskell a soutenu que la notation. Une valeur de ce type peut être considéré comme une paire (ou un « produit ») constitué d'un type a
et une valeur de type f a
.
Par exemple, si vous avez une valeur de type exists a. [a]
, vous avez une liste d'éléments d'un certain type. Il pourrait être tout type, mais même si vous ne savez pas ce qu'il est, il y a beaucoup que vous pouvez faire à la liste d'un tel. Vous pouvez inverser, ou vous pouvez compter le nombre d'éléments, ou d'effectuer toute autre opération de liste qui ne dépend pas du type des éléments.
OK, donc attendez une minute. Pourquoi Haskell utiliser forall
pour désigner un type « existentiel » comme ce qui suit?
data ShowBox = forall s. Show s => SB s
Il peut être source de confusion, mais il décrit vraiment la type de données constructeur SB
:
SB :: forall s. Show s => s -> ShowBox
Une fois construit, vous pouvez penser à une valeur de type ShowBox
comme composé de deux choses. Il est un type s
avec une valeur de type s
. En d'autres termes, il est une valeur d'un type quantifié existentiellement. ShowBox
pourrait vraiment être écrit comme exists s. Show s => s
, si Haskell a soutenu que la notation.
runST
et les amis
Étant donné que, comment sont-ils différents?
foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))
premier bar
de prendre Let. Il faut un type a
et une fonction de type a -> a
et produit une valeur de type (Char, Bool)
. Nous pourrions choisir Int
comme a
et lui donner une fonction de type Int -> Int
par exemple. Mais foo
est différent. Elle exige que la mise en œuvre de foo
pouvoir passer tout type il veut la fonction qu'on lui donne. La seule fonction que nous pouvions raisonnablement donner est id
.
Nous devrions maintenant pouvoir aborder le sens du type de runST
:
runST :: forall a. (forall s. ST s a) -> a
runST
doit être en mesure de produire une valeur de type a
, peu importe quel type nous donnons comme a
. Pour ce faire, il a besoin d'un argument de type forall s. ST s a
qui, sous le capot est juste une fonction de type forall s. s -> (a, s)
. Cette fonction doit alors être en mesure de produire une valeur de type (a, s)
peu importe quel type de la mise en œuvre runST
décide de donner comme s
.
OK, donc quoi? L'avantage est que cela met une contrainte sur l'appelant de runST
en ce que le a
type ne peut pas impliquer le s
type du tout. Vous ne pouvez pas passer une valeur de type ST s [s]
, par exemple. Ce que cela signifie en pratique que la mise en œuvre de runST
est libre d'effectuer une mutation avec la valeur du type s
. Les garanties du système de type que cette mutation est locale à la mise en œuvre de runST
.
Le type de runST
est un exemple de rang 2 types polymorphes parce que le type de son argument contient un quantificateur forall
. Le type de foo
ci-dessus est de rang 2. Un type polymorphes ordinaire, comme celui de bar
, est de rang 1, mais il devient de rang 2 si les types d'arguments doivent être polymorphes, avec leur propre quantificateur forall
. Et si une fonction prend des arguments de rang 2 alors son type de rang 3, et ainsi de suite. En général, un type qui prend des arguments polymorphes de rang n
a rang n + 1
.
La raison pour laquelle il existe différentes utilisations de ce mot-clé est qu'il est en fait utilisé dans au moins deux extensions de système de type différents. Types-rang supérieur, et existentiels
Il est probablement préférable juste pour lire et comprendre ces deux choses séparément, plutôt que d'essayer d'obtenir une explication des raisons pour lesquelles « forall » est un peu approprié de syntaxe dans les deux en même temps.
Quelqu'un peut-il expliquer complètement le mot-clé forall en anglais clair et simple (ou, si elle existe quelque part, pointez sur une telle explication claire que je l'ai manqué) qui ne suppose pas que je suis un mathématicien imprégné dans le jargon?
Je vais essayer d'expliquer tout le sens et peut-être l'application de forall
dans le contexte de Haskell et ses systèmes de type.
Mais avant de comprendre que je voudrais vous diriger vers un discours très accessible et agréable par Runar Bjarnason intitulé « Contraintes Liberate, des libertés Contraindre ". Le discours est plein d'exemples de cas d'utilisation du monde réel ainsi que des exemples de Scala pour soutenir cette déclaration, même si elle ne mentionne pas forall
. Je vais essayer d'expliquer le point de vue de forall
ci-dessous.
CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN
Il est très important à digérer et à croire cette déclaration pour procéder à l'explication suivante, donc je vous invite à regarder le discours (au moins en partie).
Maintenant un exemple très commun, montrant l'expressivité du système de type Haskell est cette signature de type:
foo :: a -> a
On dit que compte tenu de cette signature de type, il n'y a qu'une seule fonction qui peut satisfaire ce type et qui est la fonction identity
ou ce qui est plus communément connu id
.
Je me demandais toujours les fonctions ci-dessous dans les premières étapes de l'apprentissage me Haskell,:
foo 5 = 6
foo True = False
ils satisferaient la signature de type ci-dessus, alors pourquoi les gens Haskell prétendent qu'il est id
seul qui satisfait la signature de type?
C'est parce qu'il ya un forall
implicite caché dans la signature de type. Le type réel est:
id :: forall a. a -> a
Alors, laissez-nous maintenant revenons à la déclaration: Les contraintes libèrent, les libertés Contraindre
Translating que le système de type, cette affirmation devient:
Une contrainte au niveau du type, devient une liberté au niveau terme
et
Une liberté au niveau du type, devient une contrainte au niveau terme
Essayons de prouver la première déclaration:
Une contrainte au niveau du type ..
mettre donc une contrainte sur notre signature de type
foo :: (Num a) => a -> a
devient une liberté au niveau terme nous donne la liberté ou la flexibilité d'écrire tous ces
foo 5 = 6
foo 4 = 2
foo 7 = 9
...
Même peut être observé en limitant a
avec tout autre etc classe de types
Alors maintenant ce que cette signature de type: foo :: (Num a) => a -> a
traduit est:
∃a , st a -> a, ∀a ∈ Num
Ceci est connu comme la quantification existentielle, ce qui se traduit par il existe certains cas de a
pour lesquels une fonction dans l'alimentation un peu de types retourne a
quelque chose du même type, et les cas appartiennent tous à la ensemble de nombres.
On peut donc voir ajouter une contrainte (ce a
doit appartenir à l'ensemble des nombres), le niveau libère terme d'avoir plusieurs implémentations possibles.
Venons-en maintenant à la deuxième déclaration et celle qui porte en fait l'explication de forall
:
Une liberté au niveau du type, devient une contrainte au niveau terme
Alors maintenant, nous libérons la fonction au niveau du type:
foo :: forall a. a -> a
Maintenant, cela se traduit par:
∀a , a -> a
ce qui signifie que la mise en œuvre de cette signature de type doit être tel qu'il est a -> a
pour toutes les circonstances.
Alors maintenant, cela commence nous contraindre au niveau à long terme. Nous ne pouvons plus écrire
foo 5 = 7
parce que cette mise en œuvre ne satisferait pas si nous mettons a
comme Bool
. a
peut être un Char
ou un [Char]
ou un type de données personnalisé. Dans tous les cas, il doit retourner quelque chose du même type. Cette liberté au niveau du type est ce qu'on appelle Universal Quantification et la seule fonction qui peut satisfaire c'est
foo a = a
ce qui est communément connu sous le nom de la fonction identity
Par conséquent forall
est un liberty
au niveau du type, dont le but réel est de constrain
le niveau à long terme à une mise en œuvre particulière.
Comment est existentielle existentielle?
Avec Existentielle-Quantification,
forall
s dans les définitions dedata
signifient que la valeur contenue peut est de tout type approprié, non qu'il doit être tous les types appropriés . - yachiru de réponse
Une explication des raisons pour lesquelles forall
dans les définitions de data
sont isomorphes à (exists a. a)
(pseudo-Haskell) se trouve dans de Wikibooks "Haskell / types" existentiellement quantifiés.
Voici le résumé d'un bref:
data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor
Lorsque ltrage / déconstruisant MkT x
, quel est le type de x
?
foo (MkT x) = ... -- -- what is the type of x?
x
peut être tout type (comme indiqué dans le forall
), et ainsi le type de c'est:
x :: exists a. a -- (pseudo-Haskell)
Par conséquent, les points suivants sont isomorphes:
data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)
forall signifie forall
Mon interprétation simple de tout cela, est que « forall
signifie vraiment« pour tous ».
Une distinction importante à faire est l'impact de forall
sur la définition par rapport à la fonction application .
A forall
signifie définition de la valeur ou de la fonction doit être polymorphe.
Si la chose étant défini est un polymorphes valeur , cela signifie que la valeur doit être valable pour tous a
approprié, ce qui est assez restrictive.
Si la chose étant défini est un polymorphes Fonction , cela signifie que la fonction doit être valable pour tous a
approprié, ce qui est pas restrictive, car juste parce que la fonction est ne pas polymorphes signifie le paramètre étant appliqué doivent être polymorphes. Autrement dit, si la fonction est valable pour tous les a
, puis à l'inverse tout a
approprié peut être appliqué à la fonction. Cependant, le type de paramètre ne peut être choisi une fois dans la définition de la fonction.
Si un forall
est à l'intérieur du type de paramètre de fonction (c.-à-un Rank2Type
), alors cela signifie que le appliqué paramètre doit être vraiment polymorphes, pour être conforme à l'idée de des moyens de forall
définition est polymorphes. Dans ce cas, le type de paramètre peut être choisi plus d'une fois dans la définition de la fonction ( "et est choisi par la mise en œuvre de la fonction », comme sur pointé par Norman )
Par conséquent, la raison pour laquelle les définitions de data
existentielle permet tout a
est parce que le constructeur de données est un polymorphes Fonction :
MkT :: forall a. a -> T
type de MKT :: a -> *
Ce qui signifie que toute a
peut être appliqué à la fonction. Contrairement à, disons, un polymorphes valeur :
valueT :: forall a. [a]
type de VALUET :: a
Ce qui signifie que le définition de VALUET doit être polymorphes. Dans ce cas, valueT
peut être définie comme une liste vide []
de tous les types.
[] :: [t]
Différences
Bien que le sens de forall
est conforme à ExistentialQuantification
et RankNType
, a une différence existentiaux puisque le constructeur data
peut être utilisé en correspondance de motif. Comme indiqué dans le Guide de l'utilisateur GHC :
Lorsque l'appariement de motif, chaque correspondance de motif introduit une nouvelle, distincte, le type de chaque variable de type existentielle. Ces types ne peuvent pas être unifiés avec un autre type, et ils ne peuvent échapper à la portée du match de motif.