Proprio come dice il titolo: quali garanzie ci sono per una unità di ritorno della funzione Haskell da valutare? Si potrebbe pensare che non sia necessario eseguire alcun tipo di valutazione in tal caso, il compilatore potrebbe sostituire tutte queste chiamate con un ()
valore immediato a meno che non siano presenti richieste esplicite di rigore, nel qual caso il codice potrebbe dover decidere se dovrebbe ritorno ()
o fondo.
Ho sperimentato questo in GHCi, e sembra che succede il contrario, cioè una tale funzione sembra essere valutata. Un esempio molto primitivo sarebbe
f :: a -> ()
f _ = undefined
La valutazione f 1
genera un errore dovuto alla presenza di undefined
, quindi alcune valutazioni avvengono sicuramente. Tuttavia, non è chiaro quanto sia approfondita la valutazione; a volte sembra andare tanto in profondità quanto è necessario per valutare tutte le chiamate alle funzioni che ritornano ()
. Esempio:
g :: [a] -> ()
g [] = ()
g (_:xs) = g xs
Questo codice viene ripetuto indefinitamente se presentato g (let x = 1:x in x)
. Ma allora
f :: a -> ()
f _ = undefined
h :: a -> ()
h _ = ()
può essere utilizzato per mostrare che h (f 1)
restituisce ()
, quindi in questo caso non vengono valutate tutte le sottoespressioni valutate in unità. Qual è la regola generale qui?
ETA: ovviamente conosco la pigrizia. Sto chiedendo cosa impedisce agli autori di compilatori di rendere questo caso particolare ancora più pigro del solito.
ETA2: sintesi degli esempi: GHC sembra trattare ()
esattamente come qualsiasi altro tipo, cioè come se ci fosse una domanda su quale valore regolare che abita nel tipo dovrebbe essere restituito da una funzione. Il fatto che esista un solo valore non sembra (ab) utilizzato dagli algoritmi di ottimizzazione.
ETA3: quando dico Haskell, intendo Haskell come definito dal Rapporto, non Haskell-H-in-GHC. Sembra un'ipotesi non condivisa così ampiamente come immaginavo (che era "dal 100% dei lettori"), o probabilmente sarei stato in grado di formulare una domanda più chiara. Ciò nonostante, mi dispiace cambiare il titolo della domanda, poiché inizialmente chiedeva quali garanzie esistano per una tale funzione chiamata.
ETA4: sembrerebbe che questa domanda abbia fatto il suo corso e la considero senza risposta. (Stavo cercando una funzione di "domanda stretta" ma ho trovato solo "rispondi alla tua domanda" e poiché non è possibile rispondere, non ho seguito quella strada. Nessuno ha sollevato nulla dal Rapporto che lo avrebbe deciso in entrambi i modi , che sono tentato di interpretare come una risposta forte ma non definita "nessuna garanzia per la lingua in quanto tale". Tutto quello che sappiamo è che l'attuale implementazione GHC non salterà la valutazione di tale funzione.
Ho riscontrato il problema reale durante il porting di un'app OCaml su Haskell. L'app originale aveva una struttura reciprocamente ricorsiva di molti tipi e il codice dichiarava un numero di funzioni chiamate assert_structureN_is_correct
N in 1..6 o 7, ognuna delle quali restituiva unità se la struttura era effettivamente corretta e generava un'eccezione se non lo era . Inoltre, queste funzioni si chiamavano a vicenda mentre decomposivano le condizioni di correttezza. In Haskell questo è meglio gestito usando la Either String
monade, quindi l'ho trascritta in quel modo, ma la questione come questione teorica è rimasta. Grazie per tutti gli input e le risposte.
f 1
viene "sostituito" undefined
in tutti i casi.
... -> ()
può 1) terminare e restituire ()
, 2) terminare con un errore di eccezione / runtime e non riuscire a restituire nulla, oppure 3) divergere (ricorsione infinita). GHC non ottimizza il codice presupponendo che solo 1) possa accadere: se f 1
richiesto, non salta la sua valutazione e restituisce ()
. La semantica di Haskell è di valutarla e vedere cosa succede tra 1,2,3.
()
(o il tipo o il valore) in questa domanda. Tutte le stesse osservazioni accadono se si sostituisce () :: ()
, diciamo, 0 :: Int
ovunque. Queste sono solo vecchie e noiose conseguenze della valutazione pigra.
()
tipo ()
e undefined
.
h1::()->() ; h1 () = ()
eh2::()->() ; h2 _ = ()
. Esegui entrambih1 (f 1)
eh2 (f 1)
vedi che solo il primo richiede(f 1)
.