2013-08-02 12 views
16

Stworzyłem ten mały program, który tworzy długo działający thunk, który ostatecznie kończy się niepowodzeniem z wyjątkiem. Następnie wiele wątków próbuje to ocenić.Jeśli thunk powoduje wyjątek, czy wyjątek jest zachowywany w wyniku 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 

Zwiększenie liczby wątków ma wyraźnie żadnego wpływu na obliczenia, co sugeruje, że nie udało thunk utrzymuje wyjątek jako wynik. Czy to prawda? Czy to zachowanie jest udokumentowane/określone gdzieś?

Update: Zmiana linii forkFinally do

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

potwierdza, że ​​każda nić nie z wyjątkiem.

+1

Wyjątkiem * jest * wartość wyrażenia. Co jeszcze może oceniać wyrażenie wiele razy? – Carl

+0

@Carl Tego właśnie podejrzewam, ale chcę być tego pewien. Może również spróbować ponownie przeliczyć wartość ponownie. –

+0

Znam wewnętrzne GHC, w przeciwnym razie nie mógłbym stworzyć narzędzi takich jak 'ghc-heap-view', więc nie jestem pewien, czego więcej potrzebujesz. Czy możesz wyjaśnić swoje pytanie, jeśli moja odpowiedź nie jest wystarczająco pomocna? –

Odpowiedz

12

Pozwól mi odpowiedzieć na to pytanie, pokazując, jak GHC faktycznie to robi, używając biblioteki ghc-heap-view. Możesz prawdopodobnie odtworzyć to z ghc-vis i uzyskać ładne zdjęcia.

zacznę tworząc strukturę danych z wartością wyjątku gdzieś:

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

Początkowo jest czysto thunk (które wydaje się obejmować różne zajęcia typ):

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

Teraz oceń nie rzucające w stan wyjątku części:

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

Drugim elementem listy jest stil Po prostu "normalny" thunk. Teraz ocenić to uzyskać wyjątek, i spojrzeć na to jeszcze raz:

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())] 

Widać to teraz thunk, która odwołuje się do obiektu SomeException. Konstruktor danych SomeException ma typ forall e . Exception e => e -> SomeException, więc drugim parametrem konstruktora jest konstruktor DivideByZero z wyjątku ArithException i pierwszy parametr odpowiadającej instancji klasy typu typu Exception.

Ten thunk może być przekazywany podobnie jak każda inna wartość Haskella i jeśli zostanie oceniony, ponownie zgłosi wyjątek. I, podobnie jak każdej innej wartości, wyjątek mogą być udostępniane:

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) 

Te same rzeczy dzieją się z nici i MVars, nic specjalnego tam.