【问题标题】:simple example of monad that takes integers采用整数的 monad 的简单示例
【发布时间】:2026-02-02 10:10:01
【问题描述】:

我正在编写一个基于 Monads 的解释 (https://www.youtube.com/watch?v=ZhuHCtR3xq8) 组成函数 f 和 g 的 Monad。解释停在一个关键部分:要组合一个函数(比如 f)a -> ma 和另一个(比如 g)a -> ma,你需要一种将 ma 转换为 a 的方法,以便从 monad 中取出东西容器(否则你怎么能将 f 的输出输入到 g 中??),这没有得到解决。

假设我们有 f 和 g 映射一个 Integer 并返回一个 Maybe:

f :: Int -> Maybe Int
f = \x -> (Just x)

g :: Int -> Maybe Int
g = \x -> (Just x)

我想制作一个允许我组合 f 和 g 的 Monad,以便可以评估 (g o f)(x)(意思是 f(g(x)))。为此,我们需要一种将Maybe Int(f 的输出)转换为Int 的方法,以便将其发送到g。当Maybe 容器中有值时,我们只需提取该值。当 g 的输出为 Nothing 时,我们可以考虑值 0(我知道 g 的输出不能是上述 g 的 Nothing,但假设另一个 f 可以)。

这是我尝试定义执行此操作的 Monad MyMonad 的失败尝试:

f :: Int -> Maybe Int
f = \x -> (Just x)

g :: Int -> Maybe Int
g = \x -> (Just x)

data MyMonad a = Maybe a

instance Monad MyMonad where
  return x = MyMonad x
  Just x >>= (\x -> MyMonad (Just x))
  Nothing >>= (\x -> MyMonad (Just 0))

有人可以澄清这里有什么问题吗?从教程中很难知道在函数内部进行模式匹配的正确方法是什么(在这里处理 Just vs. Nothing 的情况)并梳理实例化 Monad 的所有不同语法。这个例子没有帮助 (https://wiki.haskell.org/All_About_Monads#Maybe_a_monad):在引入 bind >>= 之后,sheep 例子甚至没有使用它,而是使用了一个组合器。

【问题讨论】:

  • 为了成为Monad,类型需要是像data MyMonad a = Maybe a这样的类型构造函数。目前,你的类型有 kind *,所以它不能变成一个 monad。
  • @4castle: 谢谢修复,但我的语法有一些更根本的问题,我不确定是什么
  • comb 在您的第二个链接中绑定。
  • 您尚未将JustNothing 定义为MyMonad 的数据构造函数。此外,您在>>= 的定义中缺少= 符号和函数变量。您不能对函数进行模式匹配。
  • @4castle:你能说更多关于>>=在这种情况下的使用吗?为什么我需要等号?我不能在理论上将我所有的函数定义为 lamdas 并且从不使用等号吗?

标签: haskell functional-programming monads


【解决方案1】:

当您第一次开始学习 monad 时,最容易学习的称为 Identity monad。看起来这就是你真正想要实现的,但是你把Maybe这个词卡在某个地方,它把所有东西都扔掉了。 (也就是说,或者您实际上正在尝试实现 Maybe monad,但您尚未在任何地方定义 JustNothing。)

如果MyMonad 类似于Identity monad,那么正确的定义应该是:

f :: Int -> MyMonad Int
f = \x -> MyIdentity x

g :: Int -> MyMonad Int
g = \x -> MyIdentity x

data MyMonad a = MyIdentity a

instance Monad MyMonad where
    return x = MyIdentity x     -- return = \x -> MyIdentity x
    (MyIdentity x) >>= f = f x  -- (>>=) = \(MyIdentity x) f -> f x

如果MyMonad 类似于Maybe monad,那么正确的定义应该是:

f :: Int -> MyMonad Int
f = \x -> MyJust x

g :: Int -> MyMonad Int
g = \x -> MyJust x

data MyMonad a = MyNothing | MyJust a

instance Monad MyMonad where
    return x = MyJust x
    (MyJust x) >>= f = f x
    MyNothing  >>= f = MyNothing

(注意,要编译这些,您还需要为FunctorApplicative 定义一个实例。)

附:要使用 lambda 表示法为 Maybe monad 定义 >>=,您需要一个 case 表达式:

(>>=) = \m f -> case m of
    MyJust x  -> f x
    MyNothing -> MyNothing

【讨论】:

  • 特别要注意的是,在其中任何一个地方都没有定义MyMonad a -> a 类型的函数。 @user8379674 多次断言这是必需的,但它根本不是——事实上,对于 MyMonad 的第二个定义,用该类型定义一个合理的函数是不可能的!跨度>
  • @user8379674 我不确定我能否解释为什么不需要它,但我可以提供一些证据证明这是思考问题的不好方法。首先:如果上面的代码中没有 MyMonad a -> a 类型的函数,并且您同意上面的代码完全实现了 MyMonad 的 monad,那么这应该是 100% 令人信服的证据在实现 monad 的过程中必须定义 MyMonad a -> a 类型的函数。采取另一种策略:您似乎将m a 视为“包含”a。 (续)
  • @user8379674 但这不一定是真的。事实上,对于一些明智的单子mm a 内部根本没有a,所以m a -> a 没有意义;或者可能“包含”许多类型a的值。在这些情况下,编写m a -> a 类型的函数是不明智的,这必然会使这些值中的一个高于其他值——所有值都可能有用。在其他情况下,m a 类型的值仅潜在地“包含”a。 (续)
  • @user8379674 例如,状态单子可以被认为是“包含”单个a,但只有在对不相关的类型s 执行一些计算之后。所以要求State s a -> a 类型的函数是不明智的,因为我们必须有一个s 来计算,然后才能生成a。尽管如此,在我到目前为止提到的所有情况下,都可以将a -> m b 转换为m a -> m b,而无需先将其转换为a -> b;在某种意义上,这样做的可能性是 monad 抽象的重点。
  • @user8379674 ...并且,作为更高级别的评论:尽管我可以继续详细说明,但我认为此时抽象地谈论 monad 有点乐观。通过实现和使用几个不同的 monad(通过具体化并讨论具体的 monad),您将对 monad 的含义有更好的直觉,而不是通过全面地思考和讨论 monad。
【解决方案2】:

我认为您正在寻找 fish (>=>) 运算符(我在某些地方看到它被称为 fish,但名称并不严重)。如果我没记错的话,你想组合两个函数,每个函数都取一个二进制值来返回一个单值。所以让我们找到我们的合成函数的类型

(a -> m b) -> (b -> m c) -> (a -> mc)

恰好是>=> in Control.Monad

所以

f :: Int -> Maybe Int
f = \x -> (Just x)

g :: Int -> Maybe Int
g = \x -> (Just x)

f >=> g $ 10
Just 10

f :: Int -> [Int]
f = \x -> replicate x x

g :: Int -> [Maybe Int]
g = \x -> [Just x]
f >=> g $ 10
[Just 10,Just 10,Just 10,Just 10,Just 10,Just 10,Just 10,Just 10,Just 10,Just 10]

您还有另一条鱼<=< 向左游,这是>=> 的翻转版本。

【讨论】:

  • 你能解释一下f >=> g $ 10这行吗?似乎只是(>=>) f g 10。如果f 返回Nothing 会怎样?那么你不能把它传递给g
  • 如果我们有f :: Int -> Maybe Int; f = \x -> if x > 5 then (Just x) else Nothing 然后(>=>) f g 3 工作它变成Nothing 但它就像魔术一样。它不应该给出类型错误,因为f 返回Nothing 并传递给需要Intg
  • @user8379674 根据您的第二条评论,没有什么神奇的。如果您检查source,您会注意到>=> 实际上定义为f >=> g = \x -> f x >>= g。显然,据我所知,Maybe 类型的 Monad 类型类实例将 >>= 定义为 Nothing >>= _ = Nothing