(Réécrit pour plus de clarté)
Il semble y avoir deux parties à votre question. L'un est implicite, et demande à ce que le algébrique interprétation de l' forall
est, et l'autre est de demander à propos de la suite/Yoneda transformation, qui sdcvvc la réponse déjà couverte assez bien.
Je vais essayer de répondre à l'algébrique interprétation de l' forall
pour vous. Vous mentionnez que A -> B
est B^A
mais j'aimerais prendre une étape plus loin et d'élargir à B * B * B * ... * B
(|A|
temps). Bien que nous n'avons exponentiation comme une note répétée de la multiplication comme ça, il n'y a plus flexible de notation, ∏
(en majuscules Pi) représentant arbitraire de produits indexés. Il ya deux composantes à un Pi: la gamme de valeurs que nous voulons multiplier au cours, et l'expression qui nous sont multipliés. Par exemple, au plan de la valeur, vous pourriez exprimer la fonction factorielle en tant que fact i = ∏ [1..i] (λx -> x)
.
Aller retour dans le monde des types, nous pouvons voir l'opérateur exponentiel dans l' A -> B ~ B^A
correspondance comme un Pi: B^A ~ ∏ A (λ_ -> B)
. Il dit que nous sommes à la définition d'un A
-ary produit d' B
s, tels que l' B
s ne peut pas compter sur le particulier A
nous avons choisi. Bien sûr, c'est équivalente à une simple élévation à la puissance, mais il nous permet de passer à des cas dans lesquels il y a une dépendance.
Dans le cas le plus général, nous obtenons des types dépendants, comme ce que vous voyez dans Agda ou Coq: dans Agda syntaxe, replicate : Bool -> ((n : Nat) -> Vec Bool n)
est une application possible d'un type de Pi, ce qui peut être exprimé de façon plus explicite qu' replicate : Bool -> ∏ Nat (Vec Bool)
, ou encore en tant que replicate : ∏ Bool (λ_ -> ∏ Nat (Vec Bool))
.
Notez que comme vous pouvez le deviner à partir de l'algèbre sous-jacente, vous pouvez fusionner les deux de l' ∏
s dans la définition de l' replicate
ci-dessus en un seul ∏
allant sur le produit cartésien des domaines: ∏ Bool (\_ -> ∏ Nat (Vec Bool))
est équivalent à ∏ (Bool, Nat) (λ(_, n) -> Vec Bool n)
tout comme il le serait à la "valeur". C'est tout simplement uncurrying du point de vue de la théorie des types.
Je comprends votre question a été sur le polymorphisme, donc je vais arrêter d'aller sur des types dépendants, mais ils sont pertinents: forall
en Haskell est à peu près équivalent à un ∏
avec un domaine de plus le type (genre) de types, *
. En effet, la fonction-comme le comportement de polymorphisme peut être observée directement dans le noyau de GHC, les types comme capitale de lambda (Λ). En tant que tel, un type polymorphe comme forall a. a -> a
n'est en fait qu' ∏ * (Λ a -> (a -> a))
(à l'aide de l'Λ notation maintenant que nous faisons la distinction entre les types et les valeurs), qui peut être agrandi à l'infini de produit (Bool -> Bool, Int -> Int, () -> (), (Int -> Bool) -> (Int -> Bool), ...)
pour chaque type. L'instanciation de la variable type est tout simplement de la projection sur le bon élément de l' *
-ary produit (ou de l'application du type de fonction).
Maintenant, pour le gros morceau que j'ai raté dans ma version originale de cette réponse: parametricity. Parametricity peut être décrit de plusieurs façons différentes, mais aucun de ceux que je connais (l'affichage des types de relations, ou (di)de naturalité dans la catégorie théorie) a vraiment une très algébrique de l'interprétation. Pour nos fins, si, il se résume à quelque chose d'assez simple: vous ne pouvez pas le faire correspondre à un modèle sur *
. Je sais que GHC vous permet de le faire au niveau du type avec le type de familles, mais vous ne peut couvrir qu'une infime partie de l' *
quand faire ça, donc il y a forcément toujours des points sur lesquels votre type de famille n'est pas défini.
Ce que cela signifie, du point de vue de polymorphisme, c'est que toute fonction de type F
nous écrire en ∏ * F
doit être constante (c'est à dire, de les ignorer complètement le type dont il est polymorphe plus) ou de passer du type inchangés. Ainsi, ∏ * (Λ _ -> B)
est valide parce qu'il ignore son argument, et correspond à l' forall a. B
. L'autre cas est quelque chose comme ∏ * (Λ x -> Maybe x)
, ce qui correspond à forall a. Maybe a
, ce qui ne l'ignore pas, le type de l'argument, mais une seule "passe à travers". En tant que tel, ∏ A
qui a une pertinence domaine A
(comme lors de l' A = *
) peuvent être considérées comme plus d'un A
-ary indexé intersection (choisir les éléments communs dans toutes les instanciations de l'index), plutôt que d'un produit.
Surtout, au plan de la valeur, les règles de parametricity éviter tout drôle de comportement qui pourrait suggérer les types sont plus grandes qu'elles ne le sont vraiment. Parce que nous n'avons pas typecase, nous ne pouvons pas construire une valeur de type forall a. B
qui fait quelque chose de différent, basé sur ce qu' a
a été instancié. Ainsi, bien que le type est techniquement une fonction * -> B
, il est toujours une fonction constante, et est donc équivalent à une seule valeur de B
. À l'aide de l' ∏
interprétation, il est en effet équivalent à une infinie *
-ary produit d' B
s, mais ceux - B
valeurs doivent toujours être identique, de sorte que le produit infini est effectivement grand comme un seul B
.
De même, bien qu' ∏ * (Λ x -> (x -> x))
(un.k.un., forall a. a -> a
) est techniquement équivalent à un produit infini de fonctions, aucune de ces fonctions peut inspecter le type, de sorte que tous sont contraints de retourner uniquement leur valeur d'entrée et ne pas faire tout drôle d'affaires comme (+1) : Int -> Int
lorsqu'il est instancié à l' Int
. Car il y a un seul (en supposant un total de langage) fonction qui ne peut pas inspecter le type de son argument, mais doit renvoyer une valeur du même type, le produit infini est donc tout aussi grand comme une valeur unique.
Maintenant, à propos de votre question directe sur (forall r . (a -> r) -> r) ~ a
. Tout d'abord, laissez exprimer votre ~
de l'opérateur de façon plus formelle. C'est vraiment isomorphisme, donc nous avons besoin de deux fonctions de va-et-vient, et un argument qu'ils sont inverses.
data Iso a b = Iso
{ to :: a -> b
, from :: b -> a
-- proof1 :: forall x. to (from x) == x
-- proof2 :: forall x. from (to x) == x
}
et maintenant nous exprimer votre question de départ, en termes plus formels. Votre question revient à construire un terme de la suite (impredicative, afin de GHC a de la difficulté avec elle, mais nous allons survivre) type:
forall a. Iso (forall r. (a -> r) -> r) a
Qui, à l'aide de mon ancienne terminologie, les montants d' ∏ * (Λ a -> Iso (∏ * (Λ r -> ((a -> r) -> r))) a)
. Une fois de plus, nous avons un produit infini qui ne peut pas inspecter son type d'argument. Par handwaving, nous pouvons affirmer que les seules valeurs possibles compte tenu de la parametricity règles (les deux autres épreuves sont respectés automatiquement) to
et from
sont ($ id)
et flip id
.
Si c'est insuffisant, c'est probablement parce que l'interprétation algébrique de forall
n'a pas vraiment de rien ajouter à la preuve. C'est vraiment juste un bon vieux type de théorie, mais j'espère que j'ai été en mesure de fournir quelque chose qui se sent un peu moins catégorique que la Yoneda forme de celui-ci. Il est intéressant de noter que nous n'avons pas réellement besoin d'parametricity à écrire proof1
et proof2
- dessus. Parametricity seulement entre l'image lorsque nous voulons affirmer qu' ($ id)
et flip id
sont nos seules options pour to
et from
(dont nous ne pouvons pas prouver dans Agda ou Coq, pour cette raison).