Какова самая быстрая ошибка монады в haskell?

Может быть, или монада значительно замедляет дело. Использует ли какое-то продолжение монаду для обработки ошибок, ускоряет работу? Есть ли такая вещь, как "встроенная монада продолжения" или "монада ошибок buitin"? По встроенному я имею в виду что-то вроде ST.

Benchmark:

import Criterion.Main                                          

unsafeDiv x 0 = error "division by zero"                       
unsafeDiv x y = x `div` y                                      

safeDiv x 0 = Nothing                                          
safeDiv x y = Just (x `div` y)                                 

test1 :: Int -> [Int]                                          
test1 n = map (n `unsafeDiv`) [n,n-1..1]                       

test2 :: Int -> Maybe [Int]                                    
test2 n = mapM (n `safeDiv`) [n-1,n-2..0]                      

test3 :: Int -> Maybe [Int]                                    
test3 n = test3' Just [n-1,n-2..0]                             
  where test3' k []     = k []                                 
        test3' k (0:ns) = Nothing                              
        test3' k (n:ns) = test3' (k . (n:)) ns                 

main = defaultMain                                             
  [ bench "test1" (nf test1 100000)                            
  , bench "test2" (nf test2 100000)                            
  , bench "test3" (nf test3 100000)                            
  ] 

Ответ 1

У меня был некоторый успех с довольно ужасной рукописной монадой, которая использует что-то вроде

newtype M r a = M { runM :: r -> (# Bool, a #) }

где я рассматриваю Bool как конструктор Maybe, а в случае Nothing - ошибку для a. Обычно я использую это, когда у меня больше структуры (среда e, состояние, журналы и т.д.), Поэтому я не уверен, насколько хорошо он окупится, когда это будет так просто, но монада выглядит примерно так:

instance Monad (M r) where
  return a = M (\_ -> (# True, a #))
  M f >>= k = M (\r -> case f r of
    (# True, a #) -> runM (k a) r
    (# False, _ #) -> (# False, undefined #))
  fail _ = M (\_ -> (# False, undefined #))

Это имеет то преимущество, что мы не создаем ничего в куче, просто стек.

Однако вам нужно быть осторожным, чтобы быть строгим во всех правильных местах. Легко случайно создать thunk в вашем состоянии, которое может убить вашу производительность.

Если вы чувствуете смелость, вы можете провести контрабанду unsafeCoerce d в слоте "a" при сбое, а также извлечь его в конце или просто перевести Bool в Maybe e, но вам нужно быть осторожным, потому что вы не хотите строить башню небезопасных коров, победив всю работу, которую вы прошли, чтобы получить эту отдачу.

Эффективность этого подхода зависит от того, насколько накладные расходы на строительство и разрывы. Возможно, кадры - это затраты на умственные и временные затраты на распространение кода для устранения сбоев в разных местах кода. Обратите внимание, что → = необходимо эффективно отключить случай сбоя вручную.

Ответ 2

Обычно использование Maybe/Либо не должно замедлять работу. Однако, если "Возможно" / "На самом деле это ваше узкое место", вы можете попробовать использовать монаду Maybe на основе CPS, как в пакете contstuff. Другая возможность - использовать Монаду Конда с эвакуационными маршрутами.

В любом случае, я не считаю, что "Возможно" и "Либо" являются узкими местами. Вы можете использовать их неправильно, например, заставляя слишком много. Общим недоверием является то, что все проблемы с производительностью можно решить, используя seq.

Ответ 3

Control.Exception предоставляет try/catch/finally в монаде IO. Это делает их пригодными для использования в монаде ST также (при условии, что вы осторожны). Формулы броска можно использовать в чистом коде. Я подозреваю (хотя я еще не проверял) механизм исключения эффективен. Хотя это не то же самое, что использование трансформатора монады для обеспечения потока контроля отказа, иногда исключения являются правильным решением.

Ответ 4

Есть ли такая вещь, как "встроенная монада продолжения" или "монада ошибок buitin"?

Встроенная ошибка-монада MonadError от Control.Monad.Error, а встроенная монада продолжения MonadCont от Control.Monad.Cont.

Это не настоящие монады, а классы типов. Используйте Hoogle или :i Control.Monad.Error в GHCi для поиска экземпляров.

Выдающимся примером MonadError является Either.