J'étais un peu confus par la documentation de fix
(même si je pense que je comprends 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 pend là. Pour être honnête, c'est sur mon ancien macbook qui est plutôt 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'il ne consomme pas de temps CPU). Qu'est-ce que je fais mal?
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 voulons dire que dans le sens théorie du domaine . Donc fix (\x -> 2*x-1)
ne va pas retourner 1
, Car bien que 1
Soit un point fixe de cette fonction, ce n'est pas la moindre dans l'ordre des domaines.
Je ne peux pas décrire l'ordre des domaines dans un simple paragraphe ou deux, je vais donc vous référer au lien de la 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 donne la liste infinie [1,1..]
, On pourrait dire la même chose en utilisant fix
:
fix (\x -> 1:x)
(Ou simplement fix (1:)
), qui dit me trouver un point fixe de la fonction (1:)
, IOW une valeur x
telle que x = 1:x
... juste comme nous l'avons défini ci-dessus. Comme vous pouvez le voir dans la définition, fix
n'est 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 en utilisant fix
de cette façon:
fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))
Exercice: développez la définition de fix
pour montrer que ces deux définitions de fib
sont équivalentes.
Mais pour une compréhension complète, lisez la théorie des domaines. C'est vraiment cool.
Je ne prétends pas du tout comprendre cela, mais si cela aide quelqu'un ... alors yippee.
Considérez la définition de fix
. fix f = let x = f x in x
. La partie époustouflante est que x
est défini comme f x
. Mais réfléchissez un instant.
x = f x
Puisque x = f x, alors nous pouvons remplacer 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 terminer, f
doit générer une sorte de structure, de sorte qu'un f
plus tard puisse correspondre à cette structure et terminer la récursivité, sans vraiment se soucier de la pleine "valeur" de son paramètre (?)
À moins, bien sûr, que vous ne vouliez faire quelque chose comme créer une liste infinie, comme illustré par luqui.
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
, En d'autres termes, (b -> b) -> (b -> b)
. On peut donc dire que a = (b -> b)
. De cette façon, fix prend notre fonction, qui est a -> a
, Ou vraiment, (b -> b) -> (b -> b)
, Et retournera un résultat de type a
, en d'autres termes, b -> b
, autrement dit, une autre fonction!
Attendez, je pensais que c'était censé retourner un point fixe ... pas une fonction. Eh bien, 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écurser (par conséquent, l'un des paramètres est une fonction utilisée pour récursivement), et fix
lui a enseigné comment récurser.
Rappelez-vous comment j'ai dit que f
devait générer une sorte de structure pour qu'un f
ultérieur puisse correspondre à un motif et se terminer? Eh bien, ce n'est pas tout à fait vrai, je suppose. TomMD a illustré comment nous pouvons développer x
pour appliquer la fonction et avancer vers le cas de base. Pour sa fonction, il a utilisé un if/then, et c'est ce qui provoque la résiliation. Après des remplacements répétés, la partie in
de toute la définition de fix
cesse finalement d'être définie en termes de x
et c'est alors qu'elle est calculable et complète.
Vous avez besoin d'un moyen pour que le point fixe se termine. En développant votre exemple, il est évident qu'il 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 l'utilisation de Fix (notez que je n'utilise pas souvent Fix et que j'étais probablement fatigué/ne me préoccupais pas du code lisible lorsque j'ai écrit cela):
(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 argument fix
doit généralement être une fonction qui est le cas 'recurse' 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
Remarquez 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? Ce x
est devenu une fonction dans notre branche then
.
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 de x 2
à la fin au lieu de simplement 2
.
let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->
Et je m'arrête ici!
Une définition claire est Vous auriez pu aussi réinventer le correctif!
D'après ce que je comprends, il trouve une valeur pour la fonction, de sorte qu'il 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 ce qui a le plus d'indéfinis. Par exemple, avec id,
λ <*Main Data.Function>: id undefined
*** Exception: Prelude.undefined
Comme vous pouvez le voir, undefined est un point fixe, donc fix
le choisira. Si vous le faites à la place (\ x-> 1: x).
λ <*Main Data.Function>: undefined
*** Exception: Prelude.undefined
λ <*Main Data.Function>: (\x->1:x) undefined
[1*** Exception: Prelude.undefined
Ainsi, fix
ne peut 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? Laisse-nous essayer 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! Comme il s'agit du seul point fixe, fix
doit s'y fixer. Désolé fix
, pas de boucles infinies ou indéfini pour vous.