J'étais un peu confus par la documentation de fix
(même si je pense comprendre ce qu'il est censé faire maintenant), alors j'ai regardé le code source. Cela m'a laissé plus confus:
fix :: (a -> a) -> a
fix f = let x = f x in x
Comment cela renvoie-t-il exactement un point fixe?
J'ai décidé de l'essayer en ligne de commande:
Prelude Data.Function> fix id
...
Et ça tient là. Maintenant, pour être honnête, c'est sur mon ancien macbook qui est un peu lent. Cependant, cette fonction ne peut pas être trop coûteuse en calcul car tout ce qui est passé à id rend la même chose (sans parler du fait qu'elle ne consomme pas de temps CPU). Qu'est-ce que je fais mal?
haskell
fixpoint-combinators
letrec
Jason Baker
la source
la source
fix error
ghci et vous sentir bien dans votre peau."fix
commefix f = f (fix f)
. Court, simple, fonctionne et identique à la définition mathématique.fix (1:) !! (10^8)
. L'original le fait en mémoire constante, le vôtre prend de la mémoire linéaire (ce qui le rend un peu plus lent aussi). Autrement dit, l'utilisation du let "fait un nœud plus serré" et permet de générer une structure de données circulaire, contrairement à la vôtre.fix
aussi réinventer ! m'afix
beaucoup aidé à comprendre .Réponses:
Vous ne faites rien de mal.
fix id
est une boucle infinie.Lorsque nous disons que
fix
renvoie le point le moins fixe d'une fonction, nous l'entendons au sens de la théorie des domaines . Alors ,fix (\x -> 2*x-1)
est ne va revenir1
, parce que même si1
est un point fixe de cette fonction, il est pas moins un dans la commande de domaine.Je ne peux pas décrire l'ordre des domaines en un ou deux paragraphes, je vous renvoie donc au lien de théorie des domaines ci-dessus. C'est un excellent tutoriel, facile à lire et assez instructif. Je le recommande fortement.
Pour la vue à 10 000 pieds,
fix
est une fonction d'ordre supérieur qui encode l'idée de récursivité . Si vous avez l'expression:let x = 1:x in x
Ce qui aboutit à la liste infinie
[1,1..]
, vous pourriez dire la même chose en utilisantfix
:fix (\x -> 1:x)
(Ou simplement
fix (1:)
), qui dit me trouver un point fixe de la(1:)
fonction, IOW une valeurx
telle quex = 1:x
... tout comme nous l'avons défini ci-dessus. Comme vous pouvez le voir dans la définition, ilfix
n'y a rien de plus que cette idée - la récursivité encapsulée dans une fonction.C'est aussi un concept vraiment général de récursivité - vous pouvez écrire n'importe quelle fonction récursive de cette façon, y compris les fonctions qui utilisent la récursivité polymorphe . Ainsi, par exemple, la fonction typique de fibonacci:
fib n = if n < 2 then n else fib (n-1) + fib (n-2)
Peut être écrit de
fix
cette façon:fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))
Exercice: élargissez la définition de
fix
pour montrer que ces deux définitions defib
sont équivalentes.Mais pour une compréhension complète, lisez la théorie des domaines. C'est vraiment cool.
la source
fix id
:fix
prend une fonction de typea -> a
et renvoie une valeur de typea
. Parce qu'ilid
est polymorphe pour touta
,fix id
aura le typea
, c'est-à-dire toute valeur possible. Dans Haskell, la seule valeur qui peut être de n'importe quel type est bottom, ⊥, et ne se distingue pas d'un calcul sans terminaison. Doncfix id
produit exactement ce qu'il devrait, la valeur inférieure. Un danger defix
est que si ⊥ est un point fixe de votre fonction, alors c'est par définition le point le moins fixe, doncfix
il ne se terminera pas.undefined
est également une valeur de tout type. Vous pouvez définirfix
comme:fix f = foldr (\_ -> f) undefined (repeat undefined)
._Y f = f (_Y f)
.Je ne prétends pas du tout comprendre cela, mais si cela aide quelqu'un ... alors oui.
Considérez la définition de
fix
.fix f = let x = f x in x
. La partie ahurissante est qu'ellex
est définie commef x
. Mais pensez-y une minute.x = f x
Puisque x = fx, alors nous pouvons substituer la valeur de
x
sur le côté droit de cela, non? Ainsi donc...x = f . f $ x -- or x = f (f x) x = f . f . f $ x -- or x = f (f (f x)) x = f . f . f . f . f . f . f . f . f . f . f $ x -- etc.
Donc, l'astuce est, pour se terminer,
f
doit générer une sorte de structure, de sorte qu'unf
modèle ultérieur puisse correspondre à cette structure et terminer la récursivité, sans se soucier réellement de la "valeur" complète de son paramètre (?)À moins, bien sûr, que vous ne souhaitiez faire quelque chose comme créer une liste infinie, comme luqui l'a illustré.
L'explication factorielle de TomMD est bonne. La signature de type du correctif est
(a -> a) -> a
. La signature de type pour(\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)
est(b -> b) -> b -> b
, autrement dit,(b -> b) -> (b -> b)
. Alors on peut dire çaa = (b -> b)
. De cette façon, fix prend notre fonction, qui esta -> a
, ou vraiment,(b -> b) -> (b -> b)
et retournera un résultat de typea
, en d'autres termesb -> b
, en d'autres termes, une autre fonction!Attendez, je pensais que c'était censé renvoyer un point fixe ... pas une fonction. Eh bien c'est le cas, en quelque sorte (puisque les fonctions sont des données). Vous pouvez imaginer que cela nous a donné la fonction définitive pour trouver une factorielle. Nous lui avons donné une fonction qui ne sait pas comment récurer (par conséquent, l'un de ses paramètres est une fonction utilisée pour récurer) et lui avons
fix
appris à récurer.Rappelez-vous comment j'ai dit que cela
f
devait générer une sorte de structure pour qu'unf
modèle ultérieur puisse correspondre et se terminer? Eh bien, ce n'est pas tout à fait vrai, je suppose. TomMD a illustré comment nous pouvons développerx
pour appliquer la fonction et avancer vers le cas de base. Pour sa fonction, il a utilisé un si / alors, et c'est ce qui cause la résiliation. Après des remplacements répétés, lain
partie de la définition entière defix
cesse finalement d'être définie en termes dex
et c'est à ce moment-là qu'elle est calculable et complète.la source
Vous avez besoin d'un moyen pour que le point fixe se termine. En élargissant votre exemple, il est évident que cela ne se terminera pas:
fix id --> let x = id x in x --> id x --> id (id x) --> id (id (id x)) --> ...
Voici un exemple réel de mon utilisation de correctif (notez que je n'utilise pas souvent le correctif et que j'étais probablement fatigué / ne m'inquiétait pas du code lisible lorsque j'ai écrit ceci):
(fix (\f h -> if (pred h) then f (mutate h) else h)) q
WTF, dites-vous! Eh bien, oui, mais il y a quelques points vraiment utiles ici. Tout d'abord, votre premier
fix
argument devrait généralement être une fonction qui est le cas «récurer» et le deuxième argument est les données sur lesquelles agir. Voici le même code qu'une fonction nommée:getQ h | pred h = getQ (mutate h) | otherwise = h
Si vous êtes toujours confus, la factorielle sera peut-être un exemple plus simple:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 5 -->* 120
Notez l'évaluation:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 3 --> let x = (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x in x 3 --> let x = ... in (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 3 --> let x = ... in (\d -> if d > 0 then d * (x (d-1)) else 1) 3
Oh, tu viens de voir ça? C'est
x
devenu une fonction au sein de notrethen
succursale.let x = ... in if 3 > 0 then 3 * (x (3 - 1)) else 1) --> let x = ... in 3 * x 2 --> let x = ... in 3 * (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 2 -->
Dans ce qui précède, vous devez vous rappeler
x = f x
, d'où les deux arguments dex 2
à la fin au lieu de juste2
.let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->
Et je vais m'arrêter ici!
la source
fix
sens pour moi. Ma réponse dépend en grande partie de ce que vous avez déjà dit.id x
se réduit simplement àx
(qui se réduit ensuite àid x
). - Ensuite, dans le 2ème échantillon (fact
), lorsque lex
thunk est d'abord forcé, la valeur résultante est mémorisée et réutilisée. Le recalcul de(\recurse ...) x
se produirait avec une définition de non-partagey g = g (y g)
, pas avec cette définition de partagefix
. - J'ai effectué la modification d'essai ici - vous pouvez l'utiliser, ou je pourrais effectuer la modification si vous l'approuvez.fix id
est réduit,let x = id x in x
force également la valeur de l'applicationid x
à l'intérieur dulet
cadre (thunk), donc il se réduit àlet x = x in x
, et cela boucle. On dirait ça.fix
et Y est très claire et importante chez Haskell. Je ne vois pas à quoi sert d'afficher le mauvais ordre de réduction alors que le bon est encore plus court, beaucoup plus clair et plus facile à suivre, et reflète correctement ce qui se passe réellement.D'après ce que je comprends, il trouve une valeur pour la fonction, de sorte qu'elle génère la même chose que vous lui donnez. Le hic, c'est qu'il choisira toujours undefined (ou une boucle infinie, dans haskell, les boucles indéfinies et infinies sont les mêmes) ou tout ce qui contient le plus d'indéfini. Par exemple, avec id,
λ <*Main Data.Function>: id undefined *** Exception: Prelude.undefined
Comme vous pouvez le voir, undefined est un point fixe,
fix
nous le choisirons donc. Si vous faites plutôt (\ x-> 1: x).λ <*Main Data.Function>: undefined *** Exception: Prelude.undefined λ <*Main Data.Function>: (\x->1:x) undefined [1*** Exception: Prelude.undefined
Donc,
fix
je ne peux pas choisir undefined. Pour le rendre un peu plus connecté à des boucles infinies.λ <*Main Data.Function>: let y=y in y ^CInterrupted. λ <*Main Data.Function>: (\x->1:x) (let y=y in y) [1^CInterrupted.
Encore une fois, une légère différence. Alors, quel est le point fixe? Essayons
repeat 1
.λ <*Main Data.Function>: repeat 1 [1,1,1,1,1,1, and so on λ <*Main Data.Function>: (\x->1:x) $ repeat 1 [1,1,1,1,1,1, and so on
C'est le même! Puisque c'est le seul point fixe, il
fix
faut s'y arrêter. Désoléfix
, pas de boucles infinies ou indéfinies pour vous.la source