在 Real World Haskell monads are introduced中,通过使用Maybe
monad来避免代码在屏幕右侧移动。但是,如果案例表达式包含其他单子格,例如Either
或IO
,会发生什么?
我知道使用monad变换器可以避免使用梯子,但是它们是不是因为风格问题而过度杀伤?是否有另一种惯用和轻量级的方法来解决这个问题?
更新:以下是一个示例:
stair s = do
s <- getLine
case stepMaybe s of
Nothing -> return ()
Just s1 -> case stepEither s1 of
Left _ -> return ()
Right s2 -> case stepList s2 of
[s3:_] -> case stepMaybe s3 of
Nothing -> return ()
Just s4 -> print s4
_ -> return ()
答案 0 :(得分:10)
使用专为此目的而创建的the errors
library。它允许您统一各种失败的计算,以就常见的错误处理机制达成一致。
例如,假设您有两个计算,其中一个使用Maybe
失败,另一个使用Either String
失败:
safeHead :: [a] -> Maybe a
safeHead as = case as of
[] -> Nothing
a:_ -> Just a
safeDivide :: Double -> Double -> Either String Double
safeDivide x y = if y == 0 then Left "Divide by zero" else Right (x / y)
我们可以通过两种方式使这两个函数在相同的错误处理机制上达成一致。第一种方法是通过抑制Either
错误将Maybe
函数转换为String
。这就是hush
函数的作用:
-- Provided by the `errors` package
hush :: Either e a -> Maybe a
example1 :: [Double] -> Maybe Double
example1 xs = do
x <- safeHead xs
hush (safeDivide 4 x)
或者,如果失败,我们可以使用描述性Maybe
错误消息对String
计算进行注释。这就是note
函数的作用:
-- Also provided by the `errors` package:
note :: e -> Maybe a -> Either e a
example2 :: [Double] -> Either String Double
example2 xs = do
x <- note "Empty list" (safeHead xs)
safeDivide 4 x
errors
包还包含MaybeT
和EitherT
的转换功能。这使您可以统一所有错误处理机制,以使用您选择的目标monad。
使用您的stair
示例,您可以通过让他们就MaybeT
达成一致来简化此事:
stair = void $ runMaybeT $ do
s <- lift getLine
s4 <- hoistMaybe $ do
s1 <- stepMaybe s
s2 <- hush $ stepEither s1
s3 <- headMay $ stepList s2
stepMaybe s3
lift $ print s4
答案 1 :(得分:3)
是的。使用较小的缩进设置)但是,严肃地说,以下内容应该在您的简单示例中完成。
import Prelude hiding (mapM_)
import Data.Foldable
import Data.Maybe
stair = do
s <- getLine
mapM_ print $
stepMaybe s >>=
either (const Nothing) Just . stepEither >>=
listToMaybe . stepList >>=
stepMaybe
对于其他情况,mapM, mapM_, forM, forM_, sequence, sequence_
和Data.Foldable
的操作Data.Traversable
将是您的唯一工具链。假设您有the missing instances for Either。
然而,我仍然建议考虑变形金刚,因为它们几乎是处理不同单子组成的标准方法。