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)
]
Run Code Online (Sandbox Code Playgroud)
Edw*_*ETT 11
我用一个相当可怕的手写monad取得了一些成功
newtype M r a = M { runM :: r -> (# Bool, a #) }
Run Code Online (Sandbox Code Playgroud)
我将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 #))
Run Code Online (Sandbox Code Playgroud)
这样做的好处是我们不会在堆上构造任何东西,只是堆栈.
但是,你需要小心在所有正确的地方严格要求.很容易意外地在你的州建立一个可以扼杀你的表现的thunk.
如果你感觉大胆,你可以unsafeCoerce在失败的'a'插槽中走私d错误并在最后提取它,或者你可以将其翻译Bool成a Maybe e但你需要小心,因为你不想要建立一个不安全的塔楼,击败你所经历的所有工作,以实现这一目标.
这种方法的有效性取决于构建和拆除Maybe框架的开销与分配代码以处理代码中许多不同位置的故障的代码的精神和执行时间成本相比.注意>> =必须手动有效地展开Failure案例.
Control.Exception在IO monad中提供try/catch/finally.这使得它们也可以在ST monad中使用(假设你很小心.)throw方程可用于纯代码.我怀疑(虽然我还没有验证)异常机制是有效的.虽然与使用monad变换器提供故障控制流程不同,但有时异常是正确的解决方案.