Maybe / Either monad显着降低了速度。使用一些延续monad来处理错误会加快速度吗?有没有“内置延续monad”或“buitin error 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)
]
答案 0 :(得分:12)
通常使用Maybe / Either不应该减慢速度。但是,如果Maybe / Either确实是您的瓶颈,您可以尝试在contstuff包中使用基于CPS的Maybe monad。另一种可能性是使用具有逃生路线的Cont monad。
无论如何,我不相信Maybe和Either都是瓶颈。你可能会错误地使用它们,例如强迫太多。通常使用seq
来解决所有性能问题,这是一种常见的误解。
答案 1 :(得分:11)
我使用类似
之类的手写Monad,取得了一些成功newtype M r a = M { runM :: r -> (# Bool, a #) }
我将Bool视为Maybe构造函数,并且在Nothing案例中为'a'添加了错误。当我有更多的结构(环境e,状态,日志等)时,我通常会使用它,所以我不确定当它如此简单时会有多好,但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。
如果你感觉大胆,你可以在失败的'a'插槽中走私unsafeCoerce
d错误并在最后提取它,或者你可以将Bool
翻译成一个Maybe e
但是你需要小心,因为你不想建立一个不安全的塔楼,击败你所经历的所有工作来实现这一目标。
这种方法的有效性取决于构建和拆除可能框架的开销与分配代码以处理代码中许多不同位置的故障的代码的精神和执行时间成本相比。请注意>> =如何有效地手动解除失败案例。
答案 2 :(得分:5)
Control.Exception在IO monad中提供try / catch / finally。这使得它们也可以在ST monad中使用(假设你很小心。)throw方程可用于纯代码。我怀疑(虽然我还没有验证)异常机制是有效的。虽然与使用monad变换器提供故障控制流程不同,但有时例外是正确的解决方案。
答案 3 :(得分:4)
是否存在“内置延续monad”或“buitin error monad”这样的事情?
内置错误monad是来自MonadError
的{{1}},内置续行monad是来自Control.Monad.Error
的{{1}}。
这些不是实际的monad而是类型类。在GHCi中使用Hoogle或MonadCont
来查找实例。
Control.Monad.Cont
的一个突出实例是:i Control.Monad.Error
。