Если преобразователь приводит к исключению, сохраняется ли исключение как результат преобразователя?

Я создал эту небольшую программу, которая создает долго работающий преобразователь, который в конечном итоге завершается ошибкой с исключением. Затем несколько потоков пытаются его оценить.

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

Увеличение количества потоков явно не влияет на вычисления, что говорит о том, что неудачный преобразователь сохраняет исключение в качестве результата. Это правда? Это поведение задокументировано/указано где-нибудь?

Обновление: изменение строки forkFinally на

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

подтверждает, что каждый поток терпит неудачу с исключением.


person Petr    schedule 02.08.2013    source источник
comment
Исключение является значением выражения. Что еще может сделать многократное вычисление выражения?   -  person Carl    schedule 03.08.2013
comment
@Carl Вот что я подозреваю, но хочу быть уверен. Он также может попытаться пересчитать значение снова и снова.   -  person Petr    schedule 03.08.2013
comment
Я знаю внутренности GHC, иначе я не смог бы создавать такие инструменты, как ghc-heap-view, поэтому я не уверен, что еще вам нужно. Не могли бы вы уточнить свой вопрос, если мой ответ недостаточно полезен?   -  person Joachim Breitner    schedule 07.08.2013
comment
@JoachimBreitner Ваш ответ полезен, и я сразу же проголосовал за него. Ваш ответ, похоже, основан на наблюдении (и я не знал, что вы создаете ghc-heap-view), и я хотел бы увидеть, что какой-то хакер GHC говорит что-то вроде Да, GHC всегда работает таким образом, это его гарантированное поведение. Если такого ответа не будет, я с радостью присужу награду за вас.   -  person Petr    schedule 07.08.2013
comment
На языковом уровне таких гарантий нет. Я совершенно уверен, что GHC всегда работает таким образом, но есть ли гарантии? Я не думаю, что GHC что-то гарантирует в отношении оценки чистых терминов и может свободно дублировать и вычислять там.   -  person Joachim Breitner    schedule 08.08.2013
comment
Ура, я заработал свою первую награду на SO :-).   -  person Joachim Breitner    schedule 13.08.2013


Ответы (1)


Позвольте мне ответить на этот вопрос, показав, как GHC на самом деле делает это, используя ghc-heap-view. библиотека. Вероятно, вы можете воспроизвести это с помощью ghc-vis и получить красивые картинки.

Я начинаю с создания структуры данных где-то со значением исключения:

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

Сначала это просто преобразователь (который, кажется, включает различные классы типов):

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

Теперь я оцениваю части, не вызывающие исключений:

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

Второй элемент списка по-прежнему является «обычным» переходником. Теперь я оцениваю это, получаю исключение и снова смотрю на него:

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

Вы можете видеть, что теперь это преобразователь, который ссылается на SomeException объект. Конструктор данных SomeException имеет тип forall e . Exception e => e -> SomeException, поэтому вторым параметром конструктора является DivideByZero конструктор исключения ArithException, а первый параметр — соответствующий Exception экземпляр класса типа.

Этот преобразователь можно передавать так же, как и любое другое значение Haskell, и при оценке он снова вызовет исключение. И, как и любое другое значение, исключение можно использовать совместно:

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)

То же самое происходит с потоками и MVars, ничего особенного.

person Joachim Breitner    schedule 03.08.2013