106 votes

Comment utiliser fix et comment fonctionne-t-il ?

J'ai été quelque peu déconcerté par la documentation relative à fix (bien que je pense comprendre ce qu'il est censé faire maintenant), j'ai donc regardé le code source. Cela m'a laissé encore plus perplexe :

fix :: (a -> a) -> a
fix f = let x = f x in x

Comment cela permet-il de retrouver un point fixe ?

J'ai décidé de l'essayer en ligne de commande :

Prelude Data.Function> fix id
...

Et c'est là qu'il s'accroche. Maintenant, pour être juste, c'est sur mon vieux macbook qui est assez lent. Cependant, cette fonction ne peut pas être aussi coûteux en termes de calcul, puisque tout ce qui est transmis à id lui est rendu (sans parler du fait qu'il ne consomme pas de temps d'unité centrale). Qu'est-ce que je fais de travers ?

78 votes

La réponse de la farce est "le fixe n'a pas d'utilité réelle, il est juste là pour que vous puissiez taper". fix error dans le ghci et se sentir bien dans sa peau".

5 votes

@TomMD - C'est drôle. Je m'en souviendrai si quelqu'un me demande ce que fait fix et que je me sens mal à l'aise :-)

5 votes

J'écris généralement fix comme fix f = f (fix f) . Court, simple, efficace et identique à la définition mathématique.

106voto

luqui Points 26009

Vous ne faites rien de mal. fix id est une boucle infinie.

Lorsque nous disons que fix renvoie le plus petit point fixe d'une fonction, nous voulons dire que dans le théorie des domaines sens. Ainsi, les fix (\x -> 2*x-1) es no va revenir 1 car bien que 1 est un point fixe de cette fonction, il n'est pas le le moins dans l'ordre des domaines.

Il m'est impossible de décrire l'ordonnancement des domaines en un ou deux paragraphes, je vous renvoie donc au lien de la théorie des domaines ci-dessus. Il s'agit d'un excellent tutoriel, facile à lire et très instructif. Je le recommande vivement.

Pour la vue à 10 000 pieds d'altitude, 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:) ), ce qui signifie qu'il faut me trouver un point fixe de l'équation (1:) une valeur x tel que x = 1:x ... comme nous l'avons défini ci-dessus. Comme vous pouvez le voir dans la définition, fix n'est rien d'autre que cette idée : la récursivité encapsulée dans une fonction.

Il s'agit également d'un concept très général de récursivité : vous pouvez écrire n'importe quelle fonction récursive de cette manière, 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 manière :

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 équivalents.

Mais pour bien comprendre, lisez la théorie des domaines. C'est vraiment très intéressant.

34 votes

Voici une autre façon de voir les choses fix id : fix prend une fonction de type a -> a et renvoie une valeur de type a . Parce que id est polymorphe pour tout a , fix id aura le type a c'est-à-dire toute valeur possible. En Haskell, la seule valeur qui peut être de n'importe quel type est bottom, , et est indiscernable d'un calcul non terminant. Ainsi fix id produit exactement ce qu'il doit produire, c'est-à-dire la valeur de base. Un danger de fix est que si est un point fixe de votre fonction, alors il est par définition le point fixe de votre fonction. le moins point fixe, donc fix ne se termine pas.

4 votes

@JohnL en Haskell undefined est également une valeur de n'importe quel type. Vous pouvez définir fix comme : fix f = foldr (\_ -> f) undefined (repeat undefined) .

1 votes

@Diego votre code est équivalent à _Y f = f (_Y f) .

30voto

Dan Burton Points 26639

Je ne prétends pas comprendre cela du tout, mais si cela peut aider quelqu'un... alors youpi.

Considérons la définition de fix . fix f = let x = f x in x . Ce qui est le plus étonnant, c'est que x est défini comme suit f x . Mais réfléchissez-y un instant.

x = f x

Puisque x = f x, nous pouvons substituer la valeur de x sur le côté droit, n'est-ce pas ? 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.

L'astuce consiste donc à mettre un terme à la procédure, f doit générer une sorte de structure, de sorte qu'un f peut faire une correspondance avec cette structure et mettre fin à la récursion, sans se soucier de la "valeur" complète de son paramètre ( ?).

Sauf, bien sûr, si vous souhaitez créer une liste infinie, comme l'a illustré luqui.

L'explication factorielle de TomMD est bonne. La signature de type de Fix est (a -> a) -> a . La signature de type pour (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) es (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 renvoie un résultat du type a en d'autres termes, b -> b En d'autres termes, une autre fonction !

Attendez, je pensais qu'il était censé retourner un point fixe... et non une fonction. Et bien c'est le cas, en quelque sorte (puisque les fonctions sont des données). Vous pouvez imaginer qu'il nous a donné la fonction définitive pour trouver une factorielle. Nous lui avons donné une fonction qui ne savait pas comment réintégrer (c'est pourquoi l'un de ses paramètres est une fonction utilisée pour réintégrer), et fix lui a appris la récursivité.

Vous vous souvenez que j'ai dit que f doit générer une sorte de structure afin qu'un futur f peut-elle correspondre à un modèle et se terminer ? Ce n'est pas tout à fait exact, je suppose. TomMD a illustré la façon dont nous pouvons étendre x pour appliquer la fonction et se rapprocher du cas de base. Pour sa fonction, il a utilisé un if/then, et c'est ce qui provoque la terminaison. Après des remplacements répétés, le in partie de l'ensemble de la définition de fix finit par ne plus être défini en termes de x et c'est à ce moment-là qu'il est calculable et complet.

0 votes

Merci. C'est une explication très utile et pratique.

17voto

Thomas M. DuBuisson Points 31851

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 mon utilisation de fix (notez que je n'utilise pas souvent fix et que j'étais probablement fatigué / je ne me préoccupais pas d'un 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 première fix doit généralement être une fonction, ce qui correspond au cas "recurse", et le second argument est la donnée sur laquelle agir. Voici le même code en tant que fonction nommée :

getQ h
      | pred h = getQ (mutate h)
      | otherwise = h

Si vous n'êtes toujours pas convaincu, l'exemple de la factorielle sera peut-être plus facile à comprendre :

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, vous venez de voir ça ? C'est x est devenue une fonction à l'intérieur de notre then branche.

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 2 .

let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->

Et je m'arrête là !

0 votes

Votre réponse est ce qui a réellement fait fix me semble logique. Ma réponse dépend en grande partie de ce que vous avez déjà dit.

0 votes

@Thomas vos deux séquences de réduction sont incorrectes :) id x se réduit à x (qui se réduit alors à id x ). -- Ensuite, dans le deuxième échantillon ( fact ), lorsque le x est forcé pour la première fois, la valeur résultante est mémorisée et réutilisée. Le recalcul de (\recurse ...) x se produirait avec non-partage définition y g = g (y g) pas avec ça partage fix définition. -- J'ai fait l'édition du procès ici - vous pouvez l'utiliser, ou je peux l'éditer si vous êtes d'accord.

0 votes

En fait, lorsque fix id est réduite, let x = id x in x force également la valeur de l'application id x à l'intérieur du let (thunk), de sorte qu'il se réduit à let x = x in x et cette boucle. On dirait que c'est le cas.

10voto

VlatkoB Points 350

Une définition claire est la suivante Vous auriez pu aussi réinventer la fixation !

3voto

PyRulez Points 893

Si j'ai bien compris, il s'agit de trouver une valeur pour la fonction, de telle sorte qu'elle produise la même chose que ce que vous lui donnez. Le problème est qu'il choisira toujours undefined (ou une boucle infinie, en haskell, undefined et les boucles infinies sont identiques) ou ce qui contient le plus d'undefined. 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 faites plutôt ( \x ->1:x).

λ <*Main Data.Function>: undefined
*** Exception: Prelude.undefined
λ <*Main Data.Function>: (\x->1:x) undefined
[1*** Exception: Prelude.undefined

Así que fix ne peut pas choisir indéfini. Pour que ce soit un peu plus lié aux boucles infinies.

λ <*Main Data.Function>: let y=y in y
^CInterrupted.
λ <*Main Data.Function>: (\x->1:x) (let y=y in y)
[1^CInterrupted.

Là encore, il s'agit d'une légère différence. Quel est donc 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 la même chose ! Puisque c'est le seul point fixe, fix doit s'en contenter. Désolé fix pas de boucles infinies ni d'indéfinis pour vous.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X