在返回不同类型的函数中为monad表示法

时间:2018-10-13 17:10:47

标签: haskell monads maybe

是否可以在返回类型不是该单子的函数中为单子写do表示法?

我有一个执行大多数代码逻辑的主要功能,另一个功能是在中间进行一些计算。补充功能可能会失败,这就是为什么它返回Maybe值的原因。我正在寻找对主函数中的返回值使用do表示法。举一个通用的例子:

-- does some computation to two Ints which might fail
compute :: Int -> Int -> Maybe Int

-- actual logic 
main :: Int -> Int -> Int
main x y = do
  first <- compute x y
  second <- compute (x+2) (y+2)
  third <- compute (x+4) (y+4)
  -- does some Int calculation to first, second and third

我打算让firstsecondthird具有从Int上下文中取出的实际Maybe值,但是上面的方式使Haskell抱怨无法将Maybe IntInt的类型进行匹配。

有没有办法做到这一点?还是我朝着错误的方向前进?

请原谅我是否误用了某些术语,这对Haskell还是陌生的,我仍然想尽一切办法。

编辑

main必须返回Int,而不能包装在Maybe中,因为代码的另一部分使用main作为{{1} }。单个Int的结果可能会失败,但它们应该在compute中共同通过(即至少一个会通过),而我正在寻找的是使用main的方法表示法将它们从do中取出,对其进行一些简单的Maybe计算(例如,可能将返回的所有Int视为Nothing),并将最终值返回为{ {1}}。

3 个答案:

答案 0 :(得分:8)

那么签名本质上是错误的。结果应为Maybe Int

main :: Int -> Int -> Maybe Int
main x y = do
  first <- compute x y
  second <- compute (x+2) (y+2)
  third <- compute (x+4) (y+4)
  return (first + second + third)

例如,在这里我们return (first + second + third)return会将它们包装在Just数据构造函数中。

这是因为您的do块隐式使用了>>=中的Monad Maybe,其定义为:

instance Monad Maybe where
    Nothing >>=_ = Nothing
    (Just x) >>= f = f x
    return = Just

因此,这意味着它将确实从Just数据构造函数中“解包”值,但是如果出现Nothing,则意味着整个{{ 1}}块将为do

这或多或少为Nothing提供了便利:您可以将计算作为一连串的成功动作进行,如果这些动作之一失败,结果将为{{ 1}},否则为Monad Maybe

因此,您最终不能返回Nothing而不是Just result,因为从类型的角度来看,一个或多个计算绝对可以实现 返回Int

但是,您可以“后”处理Maybe Int块的结果,例如,如果添加一个“默认”值,该值将在计算之一为Nothing的情况下使用,例如:

do

在这种情况下,如果Nothing块返回一个import Data.Maybe(fromMaybe) main :: Int -> Int -> Int main x y = fromMaybe 0 $ do first <- compute x y second <- compute (x+2) (y+2) third <- compute (x+4) (y+4) return (first + second + third),我们将其替换为do(当然,您可以在fromMaybe :: a -> Maybe a -> a中添加另一个值作为值以防计算“失败”)。

如果要返回Nothing列表中的第一个元素0,则可以使用asum :: (Foldable t, Alternative f) => t (f a) -> f a,这样就可以编写Maybe喜欢:

Just

请注意,main仍然只能包含-- first non-failing computation import Data.Foldable(asum) import Data.Maybe(fromMaybe) main :: Int -> Int -> Int main x y = fromMaybe 0 $ asum [ compute x y compute (x+2) (y+2) compute (x+4) (y+4) ],因此您仍然需要进行一些后处理。

答案 1 :(得分:3)

Willem的回答基本上是完美的,但只是为了真正说明问题,让我们考虑一下如果您编写允许返回整数的内容会发生什么情况。

因此,您拥有类型为ng serve的{​​{1}}函数,让我们假设您的main函数的实现如下:

Int -> Int -> Int

现在,这基本上是整数除法函数compute的安全版本,如果除数为compute :: Int -> Int -> Maybe Int compute a 0 = Nothing compute a b = Just (a `div` b) ,它会返回div :: Int -> Int -> Int

如果您可以编写一个返回Nothing的主函数,则可以编写以下代码:

0

这将使Int失败并返回unsafe :: Int unsafe = main 10 (-2) ,但是现在您必须将second <- compute ...解释为不好的数字。它违反了使用Nothing monad的全部目的,该安全可靠地捕获故障。当然,您可以按照Willem的描述为Nothing设置默认值,但这并不总是合适的。

更一般而言,当您位于Maybe块中时,您应该仅在monad的“盒子”内思考,不要试图逃脱。在某些情况下,例如Nothing,您可能可以使用诸如doMaybe之类的功能来进行unMaybe,但一般而言并非如此。

答案 2 :(得分:1)

对于您的问题,我有两种解释,所以都可以回答它们:

Maybe Int的{​​{1}}值求和以获得Just n

要在抛出Int值的同时求和Maybe Int,可以将NothingData.Maybe.catMaybes :: [Maybe a] -> [a]结合使用,以从列表中抛出sum值:

Nothing

将第一个sum . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)] 的值Maybe Int作为Just n

要获取第一个非Int值,可以将NothinglistToMaybe :: [a] -> Maybe a结合使用,以获取catMaybes的第一个值(如果有一个或{{1} },如果没有,fromMaybe :: a -> Maybe a -> a会将Just转换为默认值:

Nothing

如果保证至少成功了一次,请改用Nothing

fromMaybe 0 . listToMaybe . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]