Haskell 避免在 return 可能的函数中进行双重包装

Haskell avoiding double-wrapped maybes in functions that return maybes

我看到这里有很多关于 Maybe 类型和组合的问题,但我很困惑,老实说,读这些让我头疼。

这是我的情况:

例如,我有一个函数:

addm a b = Just (a + b)

如何使用 addm 函数创建函数 add :: Maybe Int -> Maybe Int -> Maybe Int,而不使用模式匹配来展开 Maybes?

我试过

add x y = (addm <$> x) <*> y

但这有一个类型 Maybe Int -> Maybe Int -> Maybe (Maybe Int)

我还想尽可能避免使用标准库之外的任何东西。

编辑:在我的具体情况下,我实际上只需要一个函数 Maybe Int -> Int -> Maybe Int 所以我可以使用

add x y = x >>= addm y

成功了。不过,我仍然对原始问题的答案感到好奇。

在上面贴一个 join

Control.Monad.join :: Monad m => m (m a) -> m a -- combine two ms into one, this is why, as the meme goes, monads are monoids in the category of endofunctors

add x y = join $ addm <$> x <*> y

我想指出这样写 addm(真的,任何总是 returns Just 的函数)是不自然的。您实际上只写 add x y = (+) <$> x <*> yadd = liftA2 (+),但是当您处理真正有趣的单子代码时,将 join 放在应用样式表达式之上的一般模式很有用.

有多种写法。所有这些都涉及 Maybe 是 Monad 的事实。

也许最容易理解的方法是使用 join 函数,对于任何 Monad,该函数都会删除最外层的嵌套。这里它有类型 Maybe (Maybe a) -> Maybe a,这正是您正在寻找的,结合标准的应用运算符:

add ma mb = join $ addm <$> ma <*> mb

或者您可以使用 do 表示法以更命令的方式编写计算,看起来像变量赋值,其中 Monad 负责传播任何 Nothing 值:

add ma mb = do
    a <- ma
    b <- mb
    addm a b

或者您可以显式使用 "bind" (>>=) 运算符,这就是上面的 do 块脱糖的目的(但我发现这不如另一个明确和易于理解两个选项):

add ma mb = ma >>= \a -> mb >>= \b -> addm a b

使用 force 类型,卢克!

您的 addm 类型为 Int -> Int -> Maybe Int。你的目标是以一种会给你 Maybe Int -> Maybe Int -> Maybe Int 的方式包装它。为此,我们需要一个类型为 (Int -> Int -> Maybe Int) -> Maybe Int -> Maybe Int -> Maybe Int 的函数。如果我们 search for that type on Hoogle, although there's no results in base, there are a few results in third-party libraries. liftJoin2 and bind2 是等价的,并且两者都完全按照你的意愿行事。如果您不想为此引入新的依赖项,请检查它们的来源以了解它们是如何做到的:

bind2 :: Monad m => (a -> b -> m c) -> m a -> m b -> m c
bind2 f x y = liftA2 (,) x y >>= uncurry f
liftJoin2 :: (Monad m) => (a -> b -> m c) -> m a -> m b -> m c
liftJoin2 f ma mb =
   join (liftM2 f ma mb)

(我在这里稍微修改了 liftJoin2 以仅直接使用 base 方法,而不是 utility-ht 重命名的包装器。)