Come si usa la correzione e come funziona?


89

Ero un po 'confuso dalla documentazione per fix(anche se penso di capire cosa dovrebbe fare ora), quindi ho guardato il codice sorgente. Questo mi ha lasciato più confuso:

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

In che modo esattamente questo restituisce un punto fisso?

Ho deciso di provarlo dalla riga di comando:

Prelude Data.Function> fix id
...

Ed è appeso lì. Ad essere onesti, questo è sul mio vecchio macbook che è un po 'lento. Tuttavia, questa funzione non può essere troppo costosa dal punto di vista computazionale poiché qualsiasi cosa passata a id restituisce la stessa cosa (per non parlare del fatto che non consuma tempo di CPU). Che cosa sto facendo di sbagliato?


69
La risposta scherzosa è "la correzione non ha alcuna utilità, è solo lì in modo da poter digitare fix errorghci e sentirsi bene con se stessi".
Thomas M. DuBuisson

3
@TomMD - Divertente. Lo ricorderò se qualcuno mi chiederà mai cosa fa la correzione e mi sento scontroso. :-)
Jason Baker

2
Di solito scrivo fixcome fix f = f (fix f). Breve, semplice, funziona e identico alla definizione matematica.
newacct

20
@newacct, sì, è come la penso anch'io. Ma quello qui può portare a strutture più efficienti. Si può vedere la differenza se si valutano, ad esempio, fix (1:) !! (10^8). L'originale lo fa in memoria costante, il tuo richiede memoria lineare (il che lo rende anche un po 'più lento). Cioè, l'uso del let "lega un nodo più stretto" e consente di generare una struttura dati circolare, mentre la tua no.
luqui

23
Avresti potuto reinventare anche tu fix! mi ha aiutato a capire fixmolto.
fredoverflow

Risposte:


92

Non stai facendo niente di sbagliato. fix idè un ciclo infinito.

Quando diciamo che fixrestituisce il punto minimo fisso di una funzione, lo intendiamo nel senso della teoria dei domini . Quindi fix (\x -> 2*x-1)è non andare a tornare 1, perché, anche se 1è un punto fisso di tale funzione, non è l' almeno uno nel ordinazione dominio.

Non posso descrivere l'ordinamento del dominio in un semplice paragrafo o due, quindi ti rimando al link della teoria del dominio sopra. È un eccellente tutorial, facile da leggere e abbastanza illuminante. Lo consiglio vivamente.

Per la vista da 10.000 piedi, fixè una funzione di ordine superiore che codifica l'idea di ricorsione . Se hai l'espressione:

let x = 1:x in x

Il che risulta nella lista infinita [1,1..], potresti dire la stessa cosa usando fix:

fix (\x -> 1:x)

(O semplicemente fix (1:)), che dice trovami un punto fisso della (1:)funzione, IOW un valore xtale che x = 1:x... proprio come abbiamo definito sopra. Come puoi vedere dalla definizione,fix non è altro che questa idea: la ricorsione incapsulata in una funzione.

È anche un concetto veramente generale di ricorsione: puoi scrivere qualsiasi funzione ricorsiva in questo modo, comprese le funzioni che usano la ricorsione polimorfica . Quindi, ad esempio, la tipica funzione di fibonacci:

fib n = if n < 2 then n else fib (n-1) + fib (n-2)

Può essere scritto in fixquesto modo:

fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))

Esercizio: espandere la definizione di fixper mostrare che queste due definizioni di fibsono equivalenti.

Ma per una piena comprensione, leggi la teoria dei domini. È roba davvero interessante.


32
Ecco un modo correlato di pensare a fix id: fixaccetta una funzione di tipo a -> ae restituisce un valore di tipo a. Poiché idè polimorfico per qualsiasi a, fix idavrà il tipo a, ovvero qualsiasi valore possibile. In Haskell, l'unico valore che può essere qualsiasi tipo è bottom, ⊥, ed è indistinguibile da un calcolo senza fine. Quindi fix idproduce esattamente quello che dovrebbe, il valore inferiore. Un pericolo di fixè che se ⊥ è un punto fisso della tua funzione, allora è per definizione il punto meno fisso, quindi fixnon terminerà.
John L

4
Anche @JohnL in Haskell undefinedè un valore di qualsiasi tipo. È possibile definire fixcome: fix f = foldr (\_ -> f) undefined (repeat undefined).
didest

1
@Diego il tuo codice è equivalente a _Y f = f (_Y f).
Will Ness

25

Non pretendo di capirlo affatto, ma se questo aiuta qualcuno ... allora yippee.

Considera la definizione di fix. fix f = let x = f x in x. La parte sbalorditiva è che xè definita come f x. Ma pensaci un attimo.

x = f x

Poiché x = fx, possiamo sostituire il valore di xsul lato destro di quello, giusto? Perciò...

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.

Quindi il trucco è, per terminare, fdeve generare una sorta di struttura, in modo che un successivof modello possa abbinare quella struttura e terminare la ricorsione, senza realmente preoccuparsi del pieno "valore" del suo parametro (?)

A meno che, ovviamente, non si desideri fare qualcosa come creare un elenco infinito, come illustrato da luqui.

La spiegazione fattoriale di TomMD è buona. La firma del tipo di Fix è (a -> a) -> a. La firma tipo per (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)è (b -> b) -> b -> b, in altre parole, (b -> b) -> (b -> b). Quindi possiamo dirlo a = (b -> b). In questo modo, fix prende la nostra funzione, che è a -> a, o davvero (b -> b) -> (b -> b), e restituirà un risultato di tipo a, in altre parole,b -> b , in altre parole, un'altra funzione!

Aspetta, pensavo dovesse restituire un punto fisso ... non una funzione. Ebbene sì, più o meno (poiché le funzioni sono dati). Puoi immaginare che ci abbia dato la funzione definitiva per trovare un fattoriale. Gli abbiamo dato una funzione che non sa come ricorrere (quindi uno dei parametri è una funzione usata per ricorrere), efix insegnato come ricorrere.

Ricordi come ho detto che fdeve generare una sorta di struttura in modo che un fmodello successivo possa corrispondere e terminare? Beh, non è esattamente vero, immagino. TomMD ha illustrato come possiamo espandere xper applicare la funzione e avanzare verso il caso base. Per la sua funzione, ha usato un if / then, e questo è ciò che causa la terminazione. Dopo ripetute sostituzioni, la inparte dell'intera definizione di fixalla fine cessa di essere definita in termini di xe cioè quando è calcolabile e completa.


Grazie. Questa è una spiegazione molto utile e pratica.
kizzx2

18

È necessario un modo per terminare il fixpoint. Espandendo il tuo esempio è ovvio che non finirà:

fix id
--> let x = id x in x
--> id x
--> id (id x)
--> id (id (id x))
--> ...

Ecco un vero esempio di me che uso fix (nota che non uso spesso fix e probabilmente ero stanco / non preoccupato per il codice leggibile quando ho scritto questo):

(fix (\f h -> if (pred h) then f (mutate h) else h)) q

WTF, dici! Ebbene, sì, ma ci sono alcuni punti davvero utili qui. Prima di tutto, il tuo primo fixargomento di solito dovrebbe essere una funzione che è il caso "ricorsione" e il secondo argomento sono i dati su cui agire. Ecco lo stesso codice di una funzione denominata:

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

Se sei ancora confuso, forse il fattoriale sarà un esempio più semplice:

fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 5 -->* 120

Notare la valutazione:

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, l'hai appena visto? Questa è xdiventata una funzione all'interno del nostro thenramo.

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

In quanto sopra è necessario ricordare x = f x, da qui i due argomenti di x 2alla fine anziché solo 2.

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

E mi fermo qui!


La tua risposta è ciò che ha davvero fixsenso per me. La mia risposta dipende in gran parte da ciò che hai già detto.
Dan Burton

@Thomas entrambe le tue sequenze di riduzione non sono corrette. :) si id xriduce solo a x(che poi si riduce di nuovo a id x). - Quindi, nel 2 ° esempio ( fact), quando il xthunk viene prima forzato, il valore risultante viene ricordato e riutilizzato. Il ricalcolo di (\recurse ...) xavverrebbe con la definizione di non condivisioney g = g (y g) , non con questa definizione di condivisionefix . - Ho apportato la modifica di prova qui - puoi usarla, oppure potrei apportare la modifica se approvi.
Will Ness

in realtà, quando fix idviene ridotto, let x = id x in xforza anche il valore dell'applicazione id xall'interno del letframe (thunk), quindi si riduce a let x = x in xe questo esegue il ciclo. Sembra così.
Will Ness

Corretta. La mia risposta sta usando il ragionamento equazionale. Mostrare la riduzione alla Haskell, che si occupa di ordine di valutazione, serve solo a confondere l'esempio senza alcun vero guadagno.
Thomas M. DuBuisson

1
La domanda è contrassegnata sia con haskell che con letrec (cioè il let ricorsivo, con condivisione). La distinzione tra fixe Y è molto chiara e importante in Haskell. Non vedo cosa sia servito mostrando l'ordine di riduzione sbagliato quando quello corretto è ancora più breve, molto più chiaro e più facile da seguire e riflette correttamente ciò che sta effettivamente accadendo.
Will Ness

3

Come ho capito, trova un valore per la funzione, in modo tale che restituisca la stessa cosa che gli dai. Il problema è che sceglierà sempre undefined (o un loop infinito, in haskell, undefined e i loop infiniti sono gli stessi) o qualunque cosa abbia il più indefinito. Ad esempio, con id,

λ <*Main Data.Function>: id undefined
*** Exception: Prelude.undefined

Come puoi vedere, undefined è un punto fisso, quindi fixlo sceglierà. Se invece fai (\ x-> 1: x).

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

Quindi fixnon puoi scegliere undefined. Per renderlo un po 'più connesso a loop infiniti.

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

Ancora una volta, una leggera differenza. Allora qual è il punto fisso? Proviamo 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

È lo stesso! Poiché questo è l'unico punto fermo, fixbisogna accontentarsi. Spiacenti fix, nessun loop infinito o non definito per te.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.