2013-08-02 10 views
16

Ho creato questo piccolo programma che crea un thunk long-running che alla fine fallisce con un'eccezione. Quindi, più thread cercano di valutarlo.Se un thunk risulta in un'eccezione, l'eccezione viene mantenuta come risultato del thunk?

import Control.Monad 
import Control.Concurrent 
import Control.Concurrent.MVar 

main = do 
    let thunk = let p = product [1..10^4] 
       in if p `mod` 2 == 0 then error "exception" 
             else() 
    children <- replicateM 2000 (myForkIO (print thunk)) 
    mapM_ takeMVar children 

-- | Spawn a thread and return a MVar which can be used to wait for it. 
myForkIO :: IO() -> IO (MVar()) 
myForkIO io = do 
    mvar <- newEmptyMVar 
    forkFinally io (\_ -> putMVar mvar()) 
    return mvar 

Aumentando il numero di fili ha chiaramente alcun impatto sul calcolo, il che suggerisce che un thunk fallito mantiene l'eccezione come risultato. È vero? Questo comportamento è documentato/specificato da qualche parte?

Aggiornamento: Modificando la riga forkFinally di

forkFinally io (\e -> print e >> putMVar mvar()) 

conferma che ogni filo riesce con l'eccezione.

+1

L'eccezione * è * il valore dell'espressione. Cos'altro potrebbe fare la valutazione dell'espressione più volte? – Carl

+0

@Carl Questo è quello che sospetto, ma voglio essere sicuro. Potrebbe anche provare a ricalcolare il valore ancora e ancora. –

+0

Conosco gli interni di GHC, altrimenti non potrei creare strumenti come 'ghc-heap-view', quindi non sono sicuro di cos'altro ti serve. Puoi chiarire la tua domanda se la mia risposta non è abbastanza utile? –

risposta

12

Lasciatemi rispondere a questa domanda mostrando come GHC esegue effettivamente questa operazione, utilizzando la libreria ghc-heap-view. Probabilmente puoi riprodurlo con ghc-vis e ottenere belle foto.

comincio con la creazione di una struttura di dati con un valore di un'eccezione da qualche parte:

Prelude> :script /home/jojo/.cabal/share/ghc-heap-view-0.5.1/ghci 
Prelude> let x = map ((1::Int) `div`) [1,0] 

In un primo momento è puramente un tonfo (che sembra coinvolgere diverse classi di tipo):

Prelude> :printHeap x 
let f1 = _fun 
in (_bco [] (_bco (D:Integral (D:Real (D:Num _fun _fun _fun _fun _fun _fun _fun) (D:Ord (D:Eq _fun _fun) _fun _fun _fun _fun _fun _fun _fun) _fun) (D:Enum _fun _fun f1 f1 _fun _fun _fun _fun) _fun _fun _fun _fun _fun _fun _fun) _fun) _fun)() 

Ora valutare le parti di lancio senza eccezione:

Prelude> (head x, length x) 
(1,2) 
Prelude> System.Mem.performGC 
Prelude> :printHeap x 
[I# 1,_thunk (_fun (I# 1)) (I# 0)] 

Il secondo elemento dell'elenco è Sono solo un thunk "normale". Ora valuto questo, ottenere un'eccezione, e guardare di nuovo:

Prelude> last x 
*** Exception: divide by zero 
Prelude> System.Mem.performGC 
Prelude> :printHeap x 
[I# 1,_thunk (SomeException (D:Exception _fun (D:Show _fun _fun _fun) _fun _fun) DivideByZero())] 

Si può vedere ora è un tonfo che fa riferimento a un oggetto SomeException. Il costruttore di dati SomeException ha tipo forall e . Exception e => e -> SomeException, quindi il secondo parametro del costruttore è il costruttore DivideByZero dell'eccezione ArithException e il primo parametro l'istanza di classe di tipo Exception corrispondente.

Questo thunk può essere passato in giro come qualsiasi altro valore Haskell e, se valutato, solleva nuovamente l'eccezione. E, proprio come qualsiasi altro valore, l'eccezione può essere condiviso:

Prelude> let y = (last x, last x) 
Prelude> y 
(*** Exception: divide by zero 
Prelude> snd y 
*** Exception: divide by zero 
Prelude> System.Mem.performGC 
Prelude> :printHeap y 
let x1 = SomeException (D:Exception _fun (D:Show _fun _fun _fun) _fun _fun) DivideByZero() 
in (_thunk x1,_thunk x1) 

Le stesse cose accadono con fili e MVars, niente di speciale.