在返回不同类型的函数中对 monad 进行标记

Do notation for monad in function returning a different type

有没有办法在 return 类型不是所述 monad 的函数中为 monad 编写 do 符号?

我有一个主要功能来处理代码的大部分逻辑,并辅以另一个在中间为它进行一些计算的功能。补充功能可能会失败,这就是为什么它 returning 一个 Maybe 值。我希望在 main 函数中对 returned 值使用 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 Int 的类型与 Int.

匹配

有办法吗?还是我走错方向了?

请原谅我是否错误地使用了某些术语,我是 Haskell 的新手,并且仍在努力了解所有内容。

编辑

main 必须 return 一个 Int,而不用包裹在 Maybe 中,因为代码的另一部分使用 [=21= 的结果]作为Int。单个 compute 的结果可能会失败,但它们应该在 main 中共同通过(即至少一个会通过),而我正在寻找的是一种使用 do 的方法符号将它们从 Maybe 中取出,对它们进行一些简单的 Int 计算(例如,可能将任何 Nothing returned 视为 0),以及 return 最终值只是 Int.

好吧,签名本质上是错误的。结果应该是 Maybe Int:

main :: Int -> Int -> <b>Maybe Int</b>
main x y = do
  first <- compute x y
  second <- compute (x+2) (y+2)
  third <- compute (x+4) (y+4)
  <b>return</b> (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 数据构造函数中 "unpack" 值,但是如果 Nothing 从它出来,那么这意味着结果整个 do 块将是 Nothing.

这或多或少是 Monad Maybe 提供的便利:您可以将计算作为一系列 成功的 操作,如果其中一个失败,结果将为 Nothing,否则将为 Just result.

因此你不能在最后 return 一个 Int 而不是 Maybe Int,因为从类型的角度来看,一个或多个计算绝对是可能的可以 return一个Nothing.

然而,您可以 "post" 处理 do 块的结果,例如,如果您添加一个 "default" 值,该值将在其中一个计算是 Nothing,比如:

import Data.Maybe(fromMaybe)

main :: Int -> Int -> <b>Int</b>
main x y = <b>fromMaybe 0</b> $ do
  first <- compute x y
  second <- compute (x+2) (y+2)
  third <- compute (x+4) (y+4)
  return (first + second + third)

如果 do-block 因此 return 是 Nothing,我们将其替换为 0(您当然可以在 fromMaybe :: a -> Maybe a -> a 作为计算值 "fails").

如果你想return Maybe列表中的第一个元素即Just,那么你可以使用asum :: (Foldable t, Alternative f) => t (f a) -> f a,这样你就可以写你的 main 喜欢:

-- first <i>non</i>-failing computation

import Data.Foldable(asum)
import Data.Maybe(fromMaybe)

main :: Int -> Int -> Int
main x y = fromMaybe 0 $ <b>asum</b> [
    compute x y
    compute (x+2) (y+2)
    compute (x+4) (y+4)
]

请注意,asum 仍然只能包含 Nothing,因此您仍然需要进行一些 post 处理。

Willem 的回答基本上是完美的,但为了真正说明问题,让我们考虑一下如果您可以编写允许您 return 一个 int 的东西会发生什么。

所以您有类型为 Int -> Int -> Intmain 函数,让我们假设您的 compute 函数的实现如下:

compute :: Int -> Int -> Maybe Int
compute a 0 = Nothing
compute a b = Just (a `div` b)

现在这基本上是整数除法函数 div :: Int -> Int -> Int 的安全版本,如果除数是 0

returns a Nothing

如果你能随心所欲地编写一个 main 函数 returns 和 Int,你将能够编写以下内容:

unsafe :: Int
unsafe = main 10 (-2)

这会使 second <- compute ... 失败并且 return 成为 Nothing 但现在您必须将 Nothing 解释为一个不好的数字。它破坏了使用 Maybe monad 安全捕获失败的全部目的。当然,您可以按照 Willem 的描述为 Nothing 提供默认值,但这并不总是合适的。

更一般地说,当你在 do 块内时,你应该只在 "the box" 内思考那是 monad,不要试图逃脱。在某些情况下,例如 Maybe,您可以使用 fromMaybemaybe 等函数来执行 unMaybe,但一般情况下不行。

我对你的问题有两种解释,所以同时回答:

Just nMaybe Int值求和得到Int

要在抛出 Nothing 值的同时对 Maybe Int 求和,您可以使用 sumData.Maybe.catMaybes :: [Maybe a] -> [a] 从列表中抛出 Nothing 值:

sum . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]

获取第一个 Maybe IntJust n 作为 Int

要获取第一个非Nothing值,可以使用catMaybes结合listToMaybe :: [a] -> Maybe a to get Just the first value if there is one or Nothing if there isn't and fromMaybe :: a -> Maybe a -> aNothing转换为默认值:

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

如果您保证至少有一个成功,请改用head

head . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]