Haskell 中最快的错误单子是什么?

发布于 2024-12-16 00:38:51 字数 1329 浏览 2 评论 0原文

Maybe/Either monad 会显着减慢速度。使用一些延续单子来处理错误是否可以加快速度?是否有“内置延续单子”或“内置错误单子”之类的东西?我所说的内置是指像 ST 之类的东西。

基准:

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

The Maybe/Either monad slows things down significantly. Does the use of some continuation monad for handling errors speeds things up? Is there such a thing as a "builtin continuation monad" or a "buitin error monad"? By builtin I mean something like 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)                            
  ] 

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

握住你手 2024-12-23 00:38:51

通常使用 Maybe/Either 不会减慢速度。但是,如果 Maybe/Either 确实是您的瓶颈,您可以尝试使用基于 CPS 的 Maybe monad,如 contstuff 包中的那样。另一种可能性是将 Cont monad 与逃生路线一起使用。

无论如何,我不认为 Maybe 和 Either 是瓶颈。您可能会错误地使用它们,例如过度强迫。人们普遍错误地认为所有性能问题都可以通过使用 seq 来解决。

Normally using Maybe/Either should not slow things down. However, if Maybe/Either really is your bottleneck, you can try using a CPS-based Maybe monad like in the contstuff package. Another possibility is to use the Cont monad with escape routes.

In any case, I don't believe that Maybe and Either are the bottlenecks. You might be using them wrong for example by forcing too much. It is a common misbelief that all performance problems can be solved by using seq.

旧街凉风 2024-12-23 00:38:51

我在一个相当可怕的手写 monad 上取得了一些成功,它使用了类似

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

我将 Bool 视为 Maybe 构造函数的东西,并且在 Nothing 情况下为“a”添加了错误。当我有更多的结构(环境、状态、日志等)时,我通常会使用它,所以我不确定当它这么简单但 monad 看起来像这样时会得到多大的回报:

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,这可能会影响您的性能。

如果您有勇气,也可以在失败时将 unsafeCoerced 错误偷偷带入“a”槽,并在最后提取它,或者您也可以直接翻译 Bool 变成 Maybe e 但你需要小心,因为你不想建立一座 unsafeCoerces 塔来击败你到目前为止所经历的所有工作。

这种方法的有效性取决于构建和拆除 Maybe 框架的开销与将代码分布到代码中许多不同位置以处理故障的心理和执行时间成本。请注意 >>= 如何有效地手动展开失败案例。

I've had some success with a rather horrible hand written monad that uses something like

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

where I treat the Bool like the Maybe constructor and in the Nothing case put an error in for 'a'. I usually use this when I have more structure (an environment e, state, logs, etc.), so I'm not sure how well it would pay off when it is this simple but the monad looks something like:

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

This has the benefit that we don't construct any thing on the heap, just the stack.

However, you need to be careful to be strict in all the right places. It is easy to accidentally build a thunk in your state which can kill your performance.

If you are feeling daring you can smuggle an unsafeCoerced error through in the 'a' slot on failure as well and extract it at the end, or you can just translate the Bool into a Maybe e but you need to be careful, because you don't want to build up a tower of unsafeCoerces defeating all the work you went through to get this far.

The effectiveness of this approach depends on how much the overhead of building and tearing down Maybe frames is vs. the mental and execution time costs of distributing the code for dealing about failure across a lot of different places in the code. Note how >>= has to effectively unwind the Failure case manually.

沩ん囻菔务 2024-12-23 00:38:51

Control.Exception 在 IO monad 中提供了 try/catch/finally。这使得它们也可以在 ST monad 中使用(假设你很小心)。抛出方程可以在纯代码中使用。我怀疑(虽然我没有验证)异常机制是有效的。虽然与使用 monad 变压器提供故障控制流不同,但有时异常是正确的解决方案。

Control.Exception provides try/catch/finally in the IO monad. Which makes them usable in the ST monad as well (assuming you are careful.) The throw equations are usable in pure code. I suspect (though I have not verified) the exception mechanism is efficient. While not the same as using a monad transformer to provide failure control flow, sometimes exceptions are the right solution.

究竟谁懂我的在乎 2024-12-23 00:38:51

是否有“内置延续单子”或“内置错误单子”之类的东西?

内置错误 monad 是 Control.Monad.Error 中的 MonadError,内置延续 monad 是 Control.Monad.Cont< 中的 MonadCont /代码>。

这些不是实际的单子,而是类型类。在 GHCi 中使用 Hoogle 或 :i Control.Monad.Error 来查找实例。

MonadError 的一个突出实例是 Either

Is there such a thing as a "builtin continuation monad" or a "buitin error monad"?

The builtin error monad is MonadError from Control.Monad.Error, and builtin continuation monad is MonadCont from Control.Monad.Cont.

These are not actual monads but type classes. Use Hoogle or :i Control.Monad.Error in GHCi to look for instances.

A prominent instance of MonadError is Either.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文