2015-10-29 5 views
7

Devo forzare la valutazione del valore puro nella monade IO. Sto scrivendo l'interfaccia di livello superiore ai collegamenti C. Al livello inferiore ho, ad esempio, la funzione newFile e la funzione freeFile. newFile restituisce qualche id, oggetto opaco che ho definito al livello inferiore. Non puoi fare praticamente nulla con questo, ma lo per usarlo per liberare il file e calcola semplicemente qualcosa associato a quel file.Come forzare correttamente la valutazione del valore puro in IO monade?

Così, ho (semplificato):

execGetter :: FilePath -> TagGetter a -> IO a 
execGetter path g = do 
    fid <- newFile path -- ‘fid’ stands for “file id” 
    let x = runGetter g fid 
    freeFile fid 
    return x 

Questa è la versione iniziale della funzione. Dobbiamo calcolare x prima che venga chiamato freeFile. (Il codice funziona, se tolgo freeFile è tutto bene, ma voglio liberare la risorsa, si sa.)

Primo tentativo (useremo seq di valutazione “forza”):

execGetter :: FilePath -> TagGetter a -> IO a 
execGetter path g = do 
    fid <- newFile path 
    let x = runGetter g fid 
    x `seq` freeFile fid 
    return x 

Errore di segmentazione. Proseguire dritto alla documentazione di seq:

Il valore di seq a b è basso se a è basso, e comunque pari a b. seq viene generalmente introdotto per migliorare le prestazioni evitando la pigrizia non necessaria .

Una nota su ordine di valutazione: l'espressione seq a b non garantisce che a verrà valutato prima b. L'unica garanzia fornita da seq è che entrambi a e b verranno valutati prima che seq restituisca un valore . In particolare, ciò significa che b può essere valutato prima dello a. Se è necessario garantire un ordine specifico di valutazione, è necessario utilizzare la funzione pseq dal pacchetto "parallelo".

Una nota positiva, infatti, ho visto persone che rivendicano cose diverse sull'ordine di valutazione in questo caso. Che mi dici di pseq? Devo dipendere da parallel solo per il pseq, hmm ... potrebbe esserci un altro modo.

{-# LANGUAGE BangPatterns #-} 

execGetter :: FilePath -> TagGetter a -> IO a 
execGetter path g = do 
    fid <- newFile path 
    let !x = runGetter g fid 
    freeFile fid 
    return x 

Errore di segmentazione. Bene, that answer non funziona nel mio caso . Ma suggerisce evaluate, proviamo troppo:

Control.Exception (evaluate) 
Control.Monad (void) 

execGetter :: FilePath -> TagGetter a -> IO a 
execGetter path g = do 
    fid <- newFile path 
    let x = runGetter g fid 
    void $ evaluate x 
    freeFile fid 
    return x 

Segmentation fault. Forse dovremmo usare il valore restituito da evaluate?

Control.Exception (evaluate) 
Control.Monad (void) 

execGetter :: FilePath -> TagGetter a -> IO a 
execGetter path g = do 
    fid <- newFile path 
    let x = runGetter g fid 
    x' <- evaluate x 
    freeFile fid 
    return x' 

No, cattiva idea.Forse potremmo catena seq:

execGetter :: FilePath -> TagGetter a -> IO a 
execGetter path g = do 
    fid <- newFile path 
    let x = runGetter g fid 
    x `seq` freeFile fid `seq` return x 

Questo funziona. Ma è questo il modo giusto per farlo? Forse funziona solo a causa della logica di ottimizzazione volatile ? Non lo so. Se seq si associa in questo caso a , in base a tale descrizione, entrambi x e freeFile vengono valutati quando return x restituisce il suo valore. Ma ancora, quale di questi, x o freeFile viene valutato per primo? Dal momento che non ottengo errori di seg, è necessario essere x, ma questo risultato è affidabile? Sai come forzare la valutazione di x prima del freeFilecorrettamente?

+2

'seq' non introdurrà segfaults a meno che non ci sono stati già * * segfaults lì. Sei sicuro che il problema sia * effettivamente * un segfault, o stai usando quel termine per indicare qualcosa di diverso dal solito significato? È possibile che sia 'freeFile' che sta segmentando? (Prendo atto che i due che hai dichiarato di non eseguire anche segfault non eseguono 'freeFile' - nonostante il modo in cui può sembrare a un occhio inesperto!) Inoltre, a meno che tu non stia usando il male lazy IO da qualche parte (es. readFile' o 'unsafeInterleaveIO'), non è necessario forzare' x'. –

+1

Inoltre, sospetto che potresti avere frainteso la nota su 'seq'. Prendi nota con attenzione della distinzione tra * valutare * un'azione 'IO' - cioè capire quale IO fare, in particolare - e * eseguire * l'azione - cioè eseguire l'IO effettivo. Il commento parla solo di valutazione; potresti volerlo rileggere attentamente con questa distinzione in mente! –

+4

Credo che 'x \' seq \ 'freeFile fid \' seq \ 'restituisce x' non libera il file,' seq'ing una azione IO non lo esegue - questa espressione dice "Valuta' x' e 'freeFile fid 'a WHNF prima di restituire' return x' "- ma, la valutazione di un'azione IO non la" esegue ". – user2407038

risposta

9

Un possibile problema è che newFile sta facendo un po 'pigro IO, e che runGetter è un consumatore sufficientemente pigro che in esecuzione seq sulla sua uscita non forza tutte IO newFile s' accadere in realtà. Questo può essere risolto utilizzando deepseq invece di seq:

execGetter :: NFData a => FilePath -> TagGetter a -> IO a 
execGetter path g = do 
    fid <- newFile path 
    let x = runGetter g fid 
    x `deepseq` freeFile fid 
    return x 

Un'altra possibilità che questo indirizzo è quello runGetter è la pretesa di essere puri, ma in realtà non è (ed è un produttore pigro). Tuttavia, se questo è il caso, la correzione corretta è di non usare deepseq qui, ma per eliminare gli usi di unsafePerformIO da runGetter, quindi utilizzare:

execGetter :: FilePath -> TagGetter a -> IO a 
execGetter path g = do 
    fid <- newFile path 
    x <- runGetter g fid 
    freeFile fid 
    return x 

che dovrebbe quindi lavorare senza ulteriori giocherellare con forzatura.

+4

Pensandoci, arrivo alla conclusione che tutto ciò che può essere rotto per ordine di * esecuzione * non deve mai lasciare 'IO', quindi il mio approccio alle funzioni di livello inferiore era sbagliato (questo è il mio primo progetto di bind). Cercherò di correggere i miei errori e quindi riferirò qui e probabilmente accetterò la tua risposta (sono sicuro che il secondo caso che stai descrivendo è ciò che realmente sta accadendo). – Mark

+4

Sì, l'eliminazione di 'unsafePerformIO' lo risolve. Devo dire che questo non ha alcun effetto sull'API di alto livello. – Mark

+1

@Mark Questo è il modo di haskell. La risposta corretta è spesso naturale. – PyRulez

0

La risposta di Daniel Wagner va bene per questo caso d'uso, ma a volte è anche utile valutare solo il dorso di una lista e lasciare gli elementi della lista non valutati (e talvolta le voci dell'elenco non hanno un'istanza ragionevole) NFData. Non è possibile utilizzare deepseq per questo perché valuta tutto. Tuttavia, è possibile combinare seq con questa funzione:

evalList :: [a] ->() 
evalList [] =() 
evalList (_:r) = evalList r 
+0

Oppure a breve 'evalList = foldl const()'. – user3237465