Lors de l'explication de la combinaison Y dans le contexte de HASKELL, il est généralement noté que la mise en œuvre directe ne choisit pas l'enregistrement de HASKELL en raison de son type récursif.
Par exemple, de Rosettacode :
The obvious definition of the Y Combinator in Haskell canot be used
because it contains an infinite recursive type (a = a -> b). Defining
a data type (Mu) allows this recursion to be broken.
newtype Mu a = Roll { unroll :: Mu a -> a }
fix :: (a -> a) -> a
fix = \f -> (\x -> f (unroll x x)) $ Roll (\x -> f (unroll x x))
Et en effet, la définition "évidente" ne tape pas la vérification:
λ> let fix f g = (\x -> \a -> f (x x) a) (\x -> \a -> f (x x) a) g
<interactive>:10:33:
Occurs check: cannot construct the infinite type:
t2 = t2 -> t0 -> t1
Expected type: t2 -> t0 -> t1
Actual type: (t2 -> t0 -> t1) -> t0 -> t1
In the first argument of `x', namely `x'
In the first argument of `f', namely `(x x)'
In the expression: f (x x) a
<interactive>:10:57:
Occurs check: cannot construct the infinite type:
t2 = t2 -> t0 -> t1
In the first argument of `x', namely `x'
In the first argument of `f', namely `(x x)'
In the expression: f (x x) a
(0.01 secs, 1033328 bytes)
La même limitation existe dans OCAML:
utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
Error: This expression has type 'a -> 'b but an expression was expected of type 'a
The type variable 'a occurs inside 'a -> 'b
Cependant, dans OCAML, on peut permettre des types récursifs en passant dans le commutateur -rectypes
:
-rectypes
Allow arbitrary recursive types during type-checking. By default, only recursive
types where the recursion goes through an object type are supported.
En utilisant -rectypes
, tout fonctionne:
utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
val fix : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
utop # let fact_improver partial n = if n = 0 then 1 else n*partial (n-1);;
val fact_improver : (int -> int) -> int -> int = <fun>
utop # (fix fact_improver) 5;;
- : int = 120
Étant curieux des systèmes de type et de type inférence, cela soulève quelques questions que je ne suis toujours pas capable de répondre.
t2 = t2 -> t0 -> t1
? Après avoir marché avec ce type, je suppose que le problème est que le type (t2
) se réfère à celui du côté droit?-rectypes
changer.Si ce sont vraiment de gros sujets, j'apprécierais les indicateurs à la littérature pertinente.
Premièrement, l'erreur de GHC,
GHC tente d'unifier quelques contraintes avec x
, d'abord, nous l'utilisons comme une fonction afin
x :: a -> b
Ensuite, nous l'utilisons comme valeur pour cette fonction
x :: a
Et enfin, nous l'offrons avec l'expression d'argument d'origine afin
x :: (a -> b) -> c -> d
Maintenant x x
Devient une tentative d'unifier t2 -> t1 -> t0
Cependant, nous ne pouvons pas l'unifier cela puisqu'il nécessiterait unification t2
, Le premier argument de x
, avec x
. D'où notre message d'erreur.
Ensuite, pourquoi pas les types récursifs généraux. Eh bien, le premier point à noter est la différence entre Equi et ISO Types récursifs,
mu X . Type
est exactement équivalent à l'expansion ou à la pliage arbitrairement.fold
et unfold
qui pliez et déplacez les définitions récursives des types.Maintenant, les types Equi-récursifs sont idéaux, mais sont absurdes difficiles à obtenir directement dans des types de types complexes. Il peut réellement faire une vérification de type indéchérable. Je ne connais pas tous les détails du système de type d'OCAML, mais les types entièrement équracursifs de HASKELL peuvent causer une boucle de type TYPECKER qui tente d'essayer d'unifier les types, par défaut, HASKELL s'assure que la vérification de type se termine. De plus, à Haskell, les synonymes de type Synonymes sont stupides, les types de récursifs les plus utiles seraient définis comme type T = T -> ()
_, mais sont affinés presque immédiatement à Haskell, mais vous ne pouvez pas entrer en ligne de type récursif, c'est infini! Par conséquent, les types récursifs dans HASKELL exigeraient une énorme refonte de la manière dont les synonymes sont traités, probablement ne vaut probablement pas l'effort de mettre comme une extension de langue.
Les types iso-récursifs sont un peu douloureux à utiliser, vous devez plus ou moins expliquer explicitement le vérificateur de type comment plier et déplier vos types, rendre vos programmes plus complexes à lire et à écrire.
Cependant, cela ressemble beaucoup à ce que vous faites avec votre type Mu
. Roll
est plié et unroll
est déplié. Donc, en fait, nous avons des types iso-récursifs cuits au four. Cependant, les types Equi-récursif ne sont que trop complexes, des systèmes tels que OCAML et HASKELL vous forcent à passer des récidives à travers les points de fixation de type.
Maintenant, si cela vous intéresse, je recommanderais des types et des langages de programmation. Ma copie est assise ouverte sur mes genoux car j'écris ceci pour m'assurer que j'ai la bonne terminologie :)
À Ocaml, vous devez passer -rectypes
comme paramètre sur le compilateur (ou entrez #rectypes;;
dans le nombril). À titre approfondi, cela fera désactiver "Chèque survient" lors de l'unification. La situation The type variable 'a occurs inside 'a -> 'b
ne sera plus un problème. Le système de type sera toujours "correct" (son, etc.), les arbres infinis qui surviennent comme des types sont parfois appelés "arbres rationnels". Le système de type devient plus faible, c'est-à-dire qu'il devient impossible de détecter certaines erreurs de programmeur.
Voir mon Lecture sur Lambda-Calculus (à partir de la diapositive 27) Pour plus d'informations sur les opérateurs de fixation avec des exemples dans OCAML.