exception haskell lazy-evaluation thunk

exception - Si un thunk resulta en una excepción, ¿se mantiene la excepción como resultado del thunk?



haskell lazy-evaluation (1)

Permítame responder a esta pregunta mostrando cómo GHC realmente hace esto, usando la ghc-heap-view . Probablemente puedas reproducir esto con ghc-vis y obtener buenas fotos.

Comienzo creando una estructura de datos con un valor de excepción en algún lugar:

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

Al principio es puramente un thunk (que parece involucrar varias clases de tipos):

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

Ahora evalúo las partes que no lanzan excepciones:

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

El segundo elemento de la lista sigue siendo solo un procesador "normal". Ahora evalúo esto, obtengo una excepción y la miro de nuevo:

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

Puede ver que ahora es un procesador que hace referencia a un objeto SomeException . El constructor de datos SomeException tiene el tipo forall e . Exception e => e -> SomeException forall e . Exception e => e -> SomeException , por lo que el segundo parámetro del constructor es el constructor DivideByZero de la excepción ArithException , y el primer parámetro es la instancia de clase de tipo Exception correspondiente.

Este truco se puede pasar como cualquier otro valor de Haskell y, si se evalúa, volverá a generar la excepción. Y, al igual que cualquier otro valor, la excepción se puede compartir:

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)

Las mismas cosas suceden con hilos y MVars, nada especial allí.

Creé este pequeño programa que crea un procesador de larga duración que finalmente falla con una excepción. Luego, múltiples hilos intentan evaluarlo.

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

Aumentar el número de subprocesos claramente no tiene impacto en el cálculo, lo que sugiere que un thunk fallido mantiene la excepción como resultado. ¿Es verdad? ¿Está este comportamiento documentado / especificado en algún lugar?

Actualización: Cambiando la línea forkFinally a

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

confirma que cada hilo falla con la excepción.