Je commence à comprendre comment le mot clé forall
est utilisé dans les "types existentiels" comme ceci:
data ShowBox = forall s. Show s => SB s
Ceci n’est cependant qu’un sous-ensemble de la manière dont forall
est utilisé et je ne peux tout simplement pas me faire une idée de son utilisation dans des domaines comme celui-ci:
runST :: forall a. (forall s. ST s a) -> a
Ou expliquer pourquoi ils sont différents:
foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))
Ou tout le RankNTypes
truc ...
J'ai tendance à préférer un anglais clair, sans jargon, aux langages habituels dans les milieux universitaires. La plupart des explications que j'essaie de lire à ce sujet (celles que je peux trouver grâce aux moteurs de recherche) ont ces problèmes:
runST
, foo
et bar
ci-dessus).Alors...
Passons à la question réelle. Quelqu'un peut-il expliquer complètement le mot clé forall
en anglais clair et clair (ou, s'il existe quelque part, indiquer une explication aussi claire que j'ai ratée) qui ne suppose pas que je suis un mathématicien plongé dans la jargon?
Édité pour ajouter:
Il y avait deux réponses remarquables parmi celles de qualité supérieure ci-dessous, mais malheureusement, je ne peux en choisir qu'une parmi les meilleures. La réponse de Norman était détaillé et utile, expliquant les choses de manière à montrer certains des fondements théoriques de forall
et en même temps à m'en montrer certaines des implications pratiques. réponse de yairch a couvert un domaine que personne d'autre n'a mentionné (variables de type périmé) et illustré tous les concepts avec du code et une session GHCi. Était-il possible de choisir les deux aussi bien, je le ferais. Malheureusement, je ne peux pas et, après avoir examiné attentivement les deux réponses, j'ai décidé que yairchu se démarquait légèrement de Norman à cause du code illustratif et de l'explication jointe. C’est cependant un peu injuste, car j’avais vraiment besoin des deux réponses pour comprendre cela au point que forall
ne me laisse pas un faible sentiment d’effroi lorsque je le vois dans une signature de type.
Commençons par 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 98 en clair. Il nécessite une extension pour prendre en charge le mot clé forall
.
En gros, il y a 3 différentes utilisations communes du mot clé forall
(ou du moins le semble =), et chacun a sa propre extension Haskell: ScopedTypeVariables
, RankNTypes
/Rank2Types
, ExistentialQuantification
.
Le code ci-dessus ne génère pas d'erreur de syntaxe avec l'un de ceux activés, mais uniquement les vérifications de type avec ScopedTypeVariables
activé.
Variables de type ciblées:
Les variables de type Scoped aident à spécifier les types de code dans les clauses where
. Cela donne le b
dans val :: b
Identique à celui du b
dans foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
.
Un point déroutant: vous pouvez entendre que lorsque vous omettez le forall
d'un type, il est toujours implicitement présent. ( de la réponse de Norman: "normalement ces langages omettent le forall des types polymorphes" ). Cette affirmation est correcte , mais fait référence aux autres utilisations de forall
et non à l'utilisation de ScopedTypeVariables
.
Types de rangs-N:
Commençons par le fait que mayb :: b -> (a -> b) -> Maybe a -> b
équivaut à mayb :: forall a b. b -> (a -> b) -> Maybe a -> b
, à l'exception de lorsque ScopedTypeVariables
est activée.
Cela signifie que cela fonctionne pour chaque a
et b
.
Disons 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 ce liftTup
? C'est liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
. Pour voir pourquoi, essayons de le coder:
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 en déduit-il que le Tuple doit en contenir deux du même type? Disons-leur qu'ils ne doivent pas nécessairement l'ê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 que v :: b
et liftFunc
veut un x
. Nous voulons vraiment que notre fonction obtienne une fonction qui accepte tout possible x
!
{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)
Donc, ce n'est pas liftTup
qui fonctionne pour tout x
, c'est la fonction qu'il obtient qui fonctionne.
Quantification existentielle:
Prenons 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 Rank-N-Types?
ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
Couldnt match expected type 'a' against inferred type '[Char]'
...
Avec Rank-N-Types, forall a
Signifiait que votre expression devait convenir à tous les a
s possibles. Par exemple:
ghci> length ([] :: forall a. [a])
0
Une liste vide fonctionne comme une liste de tout type.
Donc, avec la quantification existentielle, forall
s dans data
définitions signifient que, la valeur contenue peut être de tout type approprié, non pas qu'il doit être de tous les types appropriés.
Quelqu'un peut-il complètement expliquer le mot clé forall en anglais clair et clair?
Non. (Enfin, peut-être que Don Stewart le peut.)
Voici les obstacles à une explication simple et claire ou forall
:
C'est un quantificateur. Vous devez avoir au moins un peu de logique (calcul de prédicat) pour avoir vu un quantificateur universel ou existentiel. Si vous n'avez jamais vu le calcul des prédicats ou ne vous sentez pas à l'aise avec les quantificateurs (et que j'ai vu des étudiants mal préparés à des examens qualificatifs de doctorat), alors, pour vous, l'explication de forall
n'est pas simple.
C'est un type quantificateur. Si vous n'avez pas vu Système F et que vous avez l'habitude d'écrire des types polymorphes, vous allez trouver forall
déroutant. Une expérience de Haskell ou de ML ne suffit pas, car normalement ces langages omettent le forall
des types polymorphes. (Dans mon esprit, c'est une erreur de conception de langage.)
Dans Haskell en particulier, forall
est utilisé de manière que je trouve déroutant. (Je ne suis pas un théoricien des types, mais mon travail me met en contact avec un beaucoup de la théorie des types, et je suis assez à l'aise avec cela.) Pour moi, la principale source de confusion est-ce que forall
est utilisé pour coder un type que je préférerais écrire avec exists
. Cela est justifié par un peu délicat d'isomorphisme de type impliquant des quantificateurs et des flèches, et chaque fois que je veux le comprendre, je dois rechercher et résoudre moi-même l'isomorphisme.
Si vous n'êtes pas à l'aise avec l'idée d'isomorphisme de type, ou si vous n'avez pas l'habitude de penser aux isomorphismes de type, cette utilisation de forall
va vous contrecarrer.
Bien que le concept général de forall
soit toujours le même (liaison pour introduire une variable de type), les détails des différentes utilisations peuvent varier considérablement. L'anglais informel n'est pas un très bon outil pour expliquer les variations. Pour vraiment comprendre ce qui se passe, vous avez besoin de quelques 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.
Quant à vos exemples particuliers,
runST
devrait vous faire mal à la tête. Les types de rang supérieur (tout à gauche d'une flèche) sont rarement trouvés à l'état sauvage. Je vous encourage à lire le document introduisant runST
: "Threads d'état fonctionnel paresseux" . C'est un très bon article qui vous donnera une bien meilleure intuition pour le type de runST
en particulier et pour les types de rang supérieur en général. L'explication prend plusieurs pages, c'est très bien fait et je ne vais pas essayer de la condenser ici.
Considérer
foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))
Si j'appelle bar
, je peux simplement choisir n'importe quel type a
que je préfère et je peux lui transmettre une fonction de type a
à taper a
. Par exemple, je peux passer la fonction (+1)
ou la fonction reverse
. Vous pouvez penser que forall
dit "je peux maintenant choisir le type". (Le mot technique pour choisir le type est instanciant.)
Les restrictions sur l'appel de foo
sont beaucoup plus strictes: l'argument pour foo
doit est une fonction polymorphe. Avec ce type, les seules fonctions que je puisse transmettre à foo
sont id
ou à une fonction toujours divergente ou erronée, comme undefined
. La raison en est qu'avec foo
, le forall
se trouve à gauche de la flèche, de sorte que l'appelant de foo
je ne peux pas choisir ce que a
_ est — c'est plutôt la implémentation de foo
qui choisit ce que a
est. Parce que forall
se trouve à gauche de la flèche, plutôt qu'au-dessus de la flèche comme dans bar
, l'instanciation a lieu dans le corps de la fonction plutôt que sur le site de l'appel.
Résumé: A complet l'explication du mot clé forall
nécessite des calculs et ne peut être comprise que par quelqu'un qui a étudié les mathématiques. Même les explications partielles sont difficiles à comprendre sans mathématiques. Mais peut-être que mes explications partielles non mathématiques aideront un peu. Allez lire Launchbury et Peyton Jones sur runST
!
Addendum: Jargon "au-dessus", "au-dessous", "à gauche de". Celles-ci n'ont rien à voir avec les moyens d'écriture des types textual et avec tout ce qui concerne les arbres à syntaxe abstraite. Dans la syntaxe abstraite, un forall
prend le nom d'une variable de type, puis il existe un type complet "en dessous" du forall. Une flèche prend deux types (type argument et résultat) et forme un nouveau type (type de fonction). Le type d'argument est "à gauche de" la flèche; c'est l'enfant gauche de la flèche dans l'arbre de syntaxe abstraite.
Exemples:
Dans 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
le type entre parenthèses serait appelé "un forall à gauche d'une flèche". (J'utilise des types comme celui-ci dans un optimiseur sur lequel je travaille.)
Ma réponse originale:
Quelqu'un peut-il expliquer complètement le mot clé forall en anglais clair et clair
Comme Norman l'indique, il est très difficile d'expliquer clairement et clairement en anglais un terme technique issu de la théorie des types. Nous essayons tous cependant.
Il n'y a vraiment qu'une chose à retenir à propos de 'forall': elle lie les types à une certaine portée . Une fois que vous comprenez cela, tout est assez facile. C'est l'équivalent de 'lambda' (ou une forme de 'let') au niveau du type - Norman Ramsey utilise la notion de "gauche"/"ci-dessus" pour traduire ce même concept de scope en son excellent réponse .
La plupart des utilisations de 'forall' sont très simples et vous pouvez les trouver introduites dans le manuel d'utilisation du GHC, S7.8 ., En particulier l'excellent S7.8.5 sur les formulaires imbriqués de 'forall'.
En Haskell, nous laissons généralement le classeur pour les types, quand le type est universellement quantifié, comme ceci:
length :: forall a. [a] -> Int
est équivalent à:
length :: [a] -> Int
C'est ça.
Comme vous pouvez maintenant lier des variables de type à une certaine étendue, vous pouvez avoir des étendues autres que le niveau supérieur (" niversellement quantifié "), comme dans votre premier exemple, où la variable de type est uniquement visible dans la structure de données. . Cela autorise les types cachés (" types existentiels "). Ou nous pouvons avoir imbrication arbitraire de liaisons ("types de rang N").
Pour comprendre en profondeur les systèmes de types, vous devrez apprendre un peu de jargon. C'est la nature de l'informatique. Toutefois, des utilisations simples, comme ci-dessus, devraient pouvoir être saisies intuitivement, par analogie avec 'let' au niveau de la valeur. Une bonne introduction est Launchbury et Peyton Jones .
Ils sont densément bourrés d'hypothèses selon lesquelles j'ai lu les dernières informations sur les branches des mathématiques discrètes, de la théorie des catégories ou de l'algèbre abstraite qui sont populaires cette semaine. (Si je ne lis plus jamais les mots "consulter le document pour plus de détails sur la mise en oeuvre", ce sera trop tôt.)
Euh, et qu'en est-il de la simple logique de premier ordre? forall
fait assez clairement référence à quantification universelle , et dans ce contexte, le terme existentiel a également plus de sens, bien qu'il serait moins gênant s'il y avait étaient un mot clé exists
. Que la quantification soit effectivement universelle ou existentielle dépend de l'emplacement du quantificateur par rapport à l'endroit où les variables sont utilisées de quel côté de la flèche d'une fonction, ce qui est un peu déroutant.
Donc, si cela ne vous aide pas, ou si vous n'aimez pas la logique symbolique, dans une perspective de programmation plus fonctionnelle, vous pouvez considérer les variables de type comme étant simplement (implicites) type paramètres à la fonction. Les fonctions prenant les paramètres de type dans ce sens sont traditionnellement écrites en utilisant un lambda majuscule pour une raison quelconque, que j'écrirai ici sous la forme /\
.
Alors, considérons la fonction id
:
id :: forall a. a -> a
id x = x
Nous pouvons le réécrire sous la forme de lambdas, en déplaçant le "paramètre de type" hors de la signature de type et en ajoutant des annotations de type en ligne:
id = /\a -> (\x -> x) :: a -> a
Voici la même chose faite à const
:
const = /\a b -> (\x y -> x) :: a -> b -> a
Donc, votre fonction bar
pourrait ressembler à ceci:
bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)
Notez que le type de la fonction donnée à bar
en tant qu'argument dépend du paramètre de type de bar
. Considérez si vous aviez quelque chose comme ceci à la place:
bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)
Ici bar2
Applique la fonction à quelque chose de type Char
, donc donner à bar2
Tout paramètre de type autre que Char
provoquera une erreur de type.
D'autre part, voici à quoi pourrait ressembler foo
:
foo = (\f -> (f Char 't', f Bool True))
Contrairement à bar
, foo
ne prend aucun paramètre de type! Il faut une fonction qui elle-même prend un paramètre de type, puis applique cette fonction à deux types différents.
Ainsi, lorsque vous voyez un forall
dans une signature de type, considérez-le simplement comme une expression lambda pour les signatures de type . Tout comme les lambdas classiques, la portée de forall
s'étend aussi loin que possible vers la droite, jusqu’à la parenthèse englobante, et tout comme les variables liées dans un lambda normal, les variables de type liées par un forall
sont uniquement dans la portée de l'expression quantifiée.
Post scriptum: Peut-être que vous pourriez vous demander - maintenant que nous pensons à des fonctions prenant des paramètres de type, pourquoi ne pourrions-nous pas faire quelque chose de plus intéressant avec ces paramètres que de les placer dans une signature de type? La réponse est que nous pouvons!
Une fonction qui associe des variables de type à une étiquette et retourne un nouveau type est un constructeur type constructeur, que vous pourriez écrire comme ceci:
Either = /\a b -> ...
Mais nous aurions besoin d'une notation complètement nouvelle, car la manière dont ce type est écrit, comme Either a b
, Suggère déjà "d'appliquer la fonction Either
à ces paramètres".
D'autre part, une fonction qui "fait correspondre" le modèle sur ses paramètres de type, renvoyant différentes valeurs pour différents types, est une méthode d'une classe de type. Une légère expansion de 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 la syntaxe actuelle de Haskell ...
Une fonction qui "modèle" correspond à ses paramètres de type et renvoie un type existant arbitraire est un type de famille ou dépendance fonctionnelle - dans le premier cas, il même déjà ressemble beaucoup à une définition de fonction.
Voici une explication rapide et obscure en termes simples que vous connaissez probablement déjà.
Le mot clé forall
n'est en réalité utilisé que d'une manière dans Haskell. Cela signifie toujours la même chose quand on le voit.
quantification universelle
Un type universellement quantifié est un type de la forme forall a. f a
. Une valeur de ce type peut être considérée comme ne fonction qui prend un typea
comme argument et renvoie un valeur de type f a
. Sauf que dans Haskell, ces arguments de type sont passés implicitement par le système de types. Cette "fonction" doit vous donner la même valeur quel que soit le type qu'elle reçoit. La valeur est donc polymorphe .
Par exemple, considérons le type forall a. [a]
. 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 bien sûr qu'une seule implémentation possible. Il devrait vous donner la liste vide parce que a
pourrait être de n'importe quel type. La liste vide est la seule valeur de liste polymorphe dans son type d'élément (car elle ne contient aucun élément).
Ou le type forall a. a -> a
. L'appelant d'une telle fonction fournit à la fois un type a
et une valeur de type a
. L'implémentation doit alors renvoyer une valeur de même type a
. Il n'y a plus qu'une seule implémentation possible. Il devrait retourner la même valeur que celle qui lui a été donnée.
quantification existentielle
Un type quantifié de manière existentielle aurait la forme exists a. f a
, Si Haskell prenait en charge cette notation. Une valeur de ce type peut être considérée comme ne paire (ou un "produit") consistant en 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 type quelconque. Cela peut être n'importe quel type, mais même si vous ne savez pas ce que vous pouvez faire, vous pouvez faire beaucoup de choses avec une telle liste. Vous pouvez l'inverser ou compter le nombre d'éléments ou effectuer toute autre opération de liste qui ne dépend pas du type des éléments.
OK, alors attendez une minute. Pourquoi Haskell utilise-t-il forall
pour désigner un type "existentiel" comme suit?
data ShowBox = forall s. Show s => SB s
Cela peut être déroutant, mais il décrit vraiment le type du constructeur de données SB
:
SB :: forall s. Show s => s -> ShowBox
Une fois construit, vous pouvez penser à une valeur de type ShowBox
comme composée de deux choses. C'est un type s
avec une valeur de type s
. En d'autres termes, il s'agit d'une valeur d'un type existentiellement quantifié. ShowBox
pourrait vraiment être écrit comme exists s. Show s => s
, si Haskell supportait cette notation.
runST
et amis
Compte tenu de cela, en quoi sont-ils différents?
foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))
Prenons d'abord bar
. Il prend 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. Cela nécessite que l'implémentation de foo
puisse transmettre n'importe quel type souhaité à la fonction que nous lui donnons. Donc, la seule fonction que nous pourrions raisonnablement lui donner est id
.
Nous devrions maintenant être capables d’aborder le sens du type de runST
:
runST :: forall a. (forall s. ST s a) -> a
Donc, runST
doit pouvoir produire une valeur de type a
, quel que soit le type que nous donnons sous la forme a
. Pour ce faire, il lui faut un argument de type forall s. ST s a
Qui, sous le capot, n'est qu'une fonction de type forall s. s -> (a, s)
. Cette fonction doit alors être capable de produire une valeur de type (a, s)
Quel que soit le type d'implémentation de runST
décide de donner comme s
.
Okay, alors quoi? L'avantage est que cela impose une contrainte à l'appelant de runST
en ce que le type a
ne peut pas impliquer le type s
. Vous ne pouvez pas lui transmettre une valeur de type ST s [s]
, Par exemple. Cela signifie en pratique que l'implémentation de runST
est libre d'effectuer une mutation avec la valeur de type s
. Le systʻeme de types garantit que cette mutation est locale ʻa l'implémentation de runST
.
Le type de runST
est un exemple de type polymorphe de rang 2 car le type de son argument contient un forall
quantificateur. Le type de foo
ci-dessus est également de rang 2. Un type polymorphe ordinaire, comme celui de bar
, est rang 1, mais il devient rang 2 si les types d’arguments sont nécessaires être polymorphe, avec leur propre quantificateur forall
. Et si une fonction prend des arguments de rang 2, son type est rang 3, et ainsi de suite. En général, un type qui prend des arguments polymorphes de rang n
a le rang n + 1
.
La raison pour laquelle ce mot clé a différentes utilisations est qu’il est utilisé dans au moins deux extensions de système de types différentes: les types de rang supérieur et les existentiels.
Il est probablement préférable de lire et de comprendre ces deux choses séparément, plutôt que d'essayer d'obtenir une explication de la raison pour laquelle "forall" est une syntaxe appropriée à la fois.
Quelqu'un peut-il expliquer complètement le mot-clé forall en anglais clair et clair (ou, s'il existe quelque part, indiquer une explication aussi claire que j'ai manquée) qui ne suppose pas que je suis un mathématicien plongé dans le jargon?
Je vais essayer d'expliquer simplement le sens et peut-être l'application de forall
dans le contexte de Haskell et de ses systèmes de types.
Mais avant que vous compreniez que je voudrais vous diriger vers un exposé très accessible et sympathique de Runar Bjarnason intitulé " Contraintes Libérées, Libertés Contraintes ". La conversation est pleine d'exemples tirés de cas d'utilisation réels, ainsi que d'exemples dans Scala pour étayer cette affirmation, bien qu'elle ne mentionne pas forall
. Je vais essayer d'expliquer le forall
perspective ci-dessous.
CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN
Il est très important de digérer et de croire que cette déclaration se poursuit avec l'explication suivante. Je vous encourage donc à regarder la conversation (au moins une partie de celle-ci).
Voici maintenant un exemple très courant, montrant l'expressivité du système de types Haskell:
foo :: a -> a
On dit que, étant donné cette signature de type, il n'y a qu'une seule fonction qui puisse satisfaire ce type, à savoir la fonction identity
ou plus communément appelée id
.
Au début de mon apprentissage de Haskell, je me suis toujours demandé les fonctions ci-dessous:
foo 5 = 6
foo True = False
ils répondent tous les deux à la signature de type ci-dessus, alors pourquoi les gens de Haskell prétendent-ils que c’est id
seul qui satisfait la signature de type?
C'est parce qu'il y a un forall
implicite caché dans la signature de type. Le type actuel est:
id :: forall a. a -> a
Revenons maintenant à l’affirmation suivante: Les contraintes libèrent, les libertés contraignent
En traduisant cela dans le système de types, cette déclaration devient:
et
Essayons de prouver la première déclaration:
Une contrainte au niveau du type ..
Donc, mettre une contrainte sur notre signature de type
foo :: (Num a) => a -> a
devient une liberté au niveau du terme nous donne la liberté ou la flexibilité d’écrire toutes ces
foo 5 = 6
foo 4 = 2
foo 7 = 9
...
On peut observer la même chose en contraignant a
avec une autre classe, etc.
Alors maintenant, ce que cette signature de type: foo :: (Num a) => a -> a
traduit en est:
∃a , st a -> a, ∀a ∈ Num
Ceci est connu sous le nom de quantification existentielle, qui se traduit par il existe quelques instances de a
pour lesquelles une fonction alimentée avec quelque chose de type a
retourne quelque chose du même tapez, et ces instances appartiennent toutes à l'ensemble des nombres.
Nous pouvons donc voir que l'ajout d'une contrainte (que a
devrait appartenir à l'ensemble des nombres) libère le niveau de terme pour avoir plusieurs implémentations possibles.
J'en viens maintenant à la deuxième déclaration et à celle qui contient l'explication de forall
:
Une liberté au niveau du type devient une contrainte au niveau du terme
Alors maintenant, 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 l'implémentation de ce type de signature doit être telle que c'est a -> a
dans toutes les circonstances.
Alors maintenant, cela commence à nous contraindre au niveau du terme. Nous ne pouvons plus écrire
foo 5 = 7
parce que cette implémentation ne satisferait pas si nous mettions a
en tant que Bool
. a
peut être un Char
ou un [Char]
ou un type de données personnalisé. Dans toutes les circonstances, il devrait renvoyer quelque chose du même type. Cette liberté au niveau du type correspond à ce que l’on appelle la quantification universelle et la seule fonction qui puisse la satisfaire est:
foo a = a
qui est communément appelée la fonction identity
Par conséquent, forall
est un liberty
au niveau du type, dont le but réel est de constrain
au niveau du terme pour une implémentation particulière.
Avec la quantification existentielle,
forall
s dansdata
définitions signifient que, la valeur contenue peut être de tout type approprié, non pas qu'il doive être de tous types appropriés. - réponse de yachir
Une explication de la raison pour laquelle forall
dans data
définitions sont isomorphes à (exists a. a)
_ (pseudo-Haskell) peut être trouvé dans "Types quantifiés de Haskell/existentiellement" de wikibooks .
Ce qui suit est un résumé sommaire:
data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor
Lors de la mise en correspondance/déconstruction des motifs MkT x
, quel est le type de x
?
foo (MkT x) = ... -- -- what is the type of x?
x
peut être n’importe quel type (comme indiqué dans forall
), et son type est donc:
x :: exists a. a -- (pseudo-Haskell)
Par conséquent, les éléments suivants sont isomorphes:
data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)
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 et la fonction .
Un forall
signifie que la définition de la valeur ou de la fonction doit être polymorphe.
Si la chose en cours de définition est une valeur polymorphe , cela signifie que la valeur doit être valide pour tous les a
appropriés, ce qui est tout à fait normal. contraignant.
Si la chose en cours de définition est une fonction polymorphe , cela signifie que la fonction doit être valide pour tous les a
appropriés, ce qui n'est pas C’est aussi restrictif parce que ce n'est pas parce que la fonction est polymorphe que le paramètre appliqué appliqué doit être polymorphe. Autrement dit, si la fonction est valide pour tous les a
, alors inversement , tout approprié a
peut être appliqué à la fonction. Cependant, le type du paramètre ne peut être choisi qu'une seule fois dans la définition de la fonction.
Si un forall
est à l'intérieur du type du paramètre de fonction (c'est-à-dire un Rank2Type
) alors cela signifie que le paramètre appliqué doit être véritablement polymorphe, être cohérent avec l'idée de forall
signifie que la définition est polymorphe. Dans ce cas, le type du paramètre peut être choisi plusieurs fois dans la définition de la fonction ( "et est choisi par l'implémentation de la fonction", comme l'a souligné Norman )
Par conséquent, la raison pour laquelle les définitions existentielles data
autorise tout type a
est parce que le constructeur de données est polymorphe fonction :
MkT :: forall a. a -> T
type de MkT :: a -> *
Ce qui signifie que tout a
peut être appliqué à la fonction. Par opposition à, disons, une valeur polymorphe :
valueT :: forall a. [a]
type de valeurT :: a
Ce qui signifie que la définition de valeurT doit être polymorphe. Dans ce cas, valueT
peut être défini comme une liste vide []
de tous types.
[] :: [t]
Même si la signification de forall
est cohérente dans ExistentialQuantification
et RankNType
, existentials a une différence puisque le constructeur data
peut être utilisé dans la recherche de modèle. Comme indiqué dans le Guide de l'utilisateur ghc :
Lors de la mise en correspondance de modèles, chaque correspondance de modèles introduit un nouveau type distinct pour chaque variable de type existentielle. Ces types ne peuvent pas être unifiés avec un autre type, ils ne peuvent pas non plus échapper à la portée de la correspondance de modèle.