Combinazioni Y e ottimizzazioni delle chiamate di coda


20

La definizione di un combinatore Y in F # è

let rec y f x = f (y f) x

f prevede di avere come primo argomento un seguito per i sottoproblemi ricorsivi. Usando yf come continuazione, vediamo che f verrà applicato alle chiamate successive mentre possiamo svilupparci

let y f x = f (y f) x = f (f (y f)) x = f (f (f (y f))) x etc...

Il problema è che, a priori, questo schema preclude l'uso di qualsiasi ottimizzazione di coda: in effetti, potrebbe esserci qualche operazione in sospeso nelle f, nel qual caso non possiamo semplicemente mutare il frame dello stack locale associato a f.

Così :

  • da un lato, l'uso del combinatore Y richiede una continuazione esplicita diversa rispetto alla funzione stessa.
  • sull'altro per applicare il TCO, vorremmo non avere alcuna operazione in sospeso in f e chiamare solo f stesso.

Conosci un modo per riconciliare quei due? Come una Y con trucco accumulatore o una Y con trucco CPS? O un argomento che dimostra che non è possibile farlo?


Hai aggiunto le chiavi di registrazione alla tua implementazione? Dovrei pensare che ne abbia bisogno dalla mia lettura ..
Jimmy Hoffa,

Hai la prova che non ottimizza la coda? Dovrei pensare che potresti voler leggere l'IL per quella funzione e vedere, non sarei sorpreso se il compilatore fosse abbastanza intelligente da inventare qualcosa ..
Jimmy Hoffa,

nel caso di una ricorsione slegata non funziona: tuttavia è possibile riscriverla per consentire tale cosa in base al fatto che il frame dello stack viene riutilizzato attraverso la chiamata y. sì, potrebbe essere necessario vedere l'IL, nessuna esperienza in questo.
nicolas,

5
Ho fatto un account e ho ottenuto 50 punti solo per commentare qui. Questa domanda è davvero interessante. Penso che dipenda interamente da f. Possiamo vedere che ypotrebbe richiamare fcon un thunk (y f), ma come dici tu fpotresti avere qualche operazione in sospeso. Penso che sarebbe interessante sapere se esiste un combinatore separato che è più amichevole per le code. Mi chiedo se questa domanda attirerebbe maggiormente l'attenzione sul sito CS Stackexchange?
John Cartwright,

Risposte:


4

Conosci un modo per riconciliare quei due?

No, e con buona ragione, IMHO.

Il combinatore Y è un costrutto teorico ed è necessario solo per completare il turing del calcolo lambda (ricordate, non ci sono anelli nel calcolo lambda, né lambda hanno nomi che potremmo usare per la ricorsione).

Come tale, il combinatore Y è davvero affascinante.

Ma : nessuno usa effettivamente il combinatore a Y per la ricorsione effettiva! (Tranne forse per divertimento, per dimostrare che funziona davvero.)

L'ottimizzazione delle chiamate in coda, OTOH, è, come dice il nome, un'ottimizzazione. Non aggiunge nulla all'espressività di un linguaggio, è solo a causa di considerazioni pratiche come lo spazio dello stack e le prestazioni del codice ricorsivo che ci interessa.

Quindi la tua domanda è: esiste un supporto hardware per la riduzione beta? (La riduzione beta è la riduzione delle espressioni lambda, lo sai.) Ma nessun linguaggio funzionale (per quanto ne so) compila il suo codice sorgente in una rappresentazione delle espressioni lambda che verrà ridotta beta in fase di esecuzione.


2
Il combinatore a Y è come ripetere un nodo che continua a sciogliersi dopo ogni utilizzo. La maggior parte dei sistemi abbrevia questo e lega il nodo al meta-livello in modo tale che non debba mai essere riprovato.
Dan D.

1
Per quanto riguarda l'ultimo paragrafo, considera Haskell che al suo interno usa la riduzione del grafico per fare una valutazione pigra. Ma la mia preferita è la riduzione ottimale che prende sempre la strada nel reticolo Church-Rosser con le riduzioni minime alla piena forma normale. Come appare in The Optimal Implementation of Functional Programming Languages ​​di Asperti e Guerrini . Vedi anche BOHM 1.1 .
Dan D.

@DanD. Grazie per i collegamenti, li proverò più avanti in un browser compatibile con Postscript. Sicuro c'è qualcosa da imparare per me. Ma sei sicuro che l' hashell compilato riduca il grafico? Ne dubito.
Ingo,

1
In realtà utilizza la riduzione del grafico: "GHC si compila con la G-machine senza tag senza spin (STG). Questa è una macchina nozionale di riduzione del grafico (cioè una macchina virtuale che esegue riduzioni del grafico come descritto sopra)." Da ... Per ulteriori informazioni sulla macchina STG, vedere l' implementazione di linguaggi funzionali pigri di Simon Peyton Jones sull'hardware di serie: la G-machine senza spinless .
Dan D.

@DanD. nello stesso articolo che hai collegato, si legge più in basso che GHC "fa una serie di ottimizzazioni su quella rappresentazione, prima di compilarlo infine in codice macchina reale (possibilmente via C usando GCC)".
Ingo,

0

Non sono del tutto sicuro di questa risposta, ma è la migliore che potrei trovare.

Il combinatore y è intrinsecamente pigro, in linguaggi rigorosi la pigrizia deve essere aggiunta manualmente attraverso lambda extra.

let rec y f x = f (y f) x

La tua definizione sembra richiedere pigrizia per terminare, o l' (y f)argomento non finirà mai di essere valutato e dovrebbe valutare se lo abbia fusato o meno . Il sommario in un contesto pigro è più complicato, e inoltre il risultato di una (y f)composizione di funzioni ripetute senza applicazione con x. Non sono sicuro che questo abbia bisogno della memoria O (n) in cui n è la profondità della ricorsione, ma dubito che potresti ottenere lo stesso tipo di TOC possibile con qualcosa del tipo (passare a Haskell perché in realtà non lo so F #)

length acc []    = acc
length acc (a:b) = length (acc+1) b 

Se non ne sei già a conoscenza, la differenza tra foldle foldl'in Haskell potrebbe far luce sulla situazione. foldlè scritto come sarebbe fatto in una lingua impaziente. Ma invece di essere TOC, in realtà è peggio che foldrperché l'acculator immagazzina un thunk potenzialmente enorme che non può essere parzialmente valutato. (Ciò è legato al motivo per cui sia foldl che foldl 'non funzionano su elenchi infiniti.) Così nelle versioni più recenti di Haskell, è foldl'stato aggiunto che forza la valutazione dell'accumulatore ogni volta che la funzione si ripete per garantire che non venga creato un enorme thunk. Sono sicuro che http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27 può spiegarlo meglio di me.

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.