【问题标题】:Combining monads (IEnumerable and Maybe as an example)结合单子(以 IEnumerable 和 Maybe 为例)
【发布时间】:2011-12-25 23:20:11
【问题描述】:

我有一个一般性问题和一个更具体的案例问题。

一般来说,如何组合不同的 monad? monad 运算符的某些组合是否允许轻松组合?还是必须编写临时方法来组合每对可能的 monad?

作为一个具体的例子,我写了一个 Maybe monad。如何使用 IEnumerable<IMaybe<T>> ?除了手动挖掘到 LINQ 扩展中的 Maybe monad(例如:select 子句中的 if(maybe.HasValue)...)之外,是否有一种“monadic”方式将两者与各自的 Bind 等 monad 操作结合起来?

否则,如果我必须编写特定的组合方法,这样的方法是否正确?

    public static IEnumerable<B> SelectMany<A, B>(this IEnumerable<A> sequence, Func<A, IMaybe<B>> func)
    {
        return from item in sequence
               let result = func(item)
               where result.HasValue
               select result.Value;
    }


    public static IEnumerable<C> SelectMany<A, B, C>(this IEnumerable<A> sequence, Func<A, IMaybe<B>> func, Func<A, B, C> selector)
    {
        return from item in sequence
               let value = item
               let maybe = func(item)
               where maybe.HasValue
               select selector(value, maybe.Value);
    }

【问题讨论】:

标签: c# ienumerable monads


【解决方案1】:

好问题!

monad 转换器 是一种类型,它为任意基本 monad 添加一些功能,同时保留 monad-ness。可悲的是,monad 转换器在 C# 中无法表达,因为它们必须使用更高种类的类型。所以,在 Haskell 工作,

class MonadTrans (t :: (* -> *) -> (* -> *)) where
    lift :: Monad m => m a -> t m a
    transform :: Monad m :- Monad (t m)

让我们逐行回顾这一行。第一行声明一个 monad 转换器是一个 t 类型,它接受一个类型为 * -&gt; * 的参数(即,一个需要一个参数的类型)并将其转换为另一种类型的 * -&gt; *。当你意识到所有的 monad 都有 * -&gt; * 类型时,你可以看到 t 的意图是把 monad 变成其他的 monad。

下一行表示所有 monad 转换器必须支持 lift 操作,该操作采用任意 monad m 并将其提升到转换器的世界 t m

最后,transform 方法表示对于任何 monad mt m 也必须是一个 monad。我正在使用来自the constraints packageentailment 运算符:-


举个例子会更有意义。这是一个单子转换器,它将Maybe-ness 添加到任意基本单子mnothing 运算符允许我们中止计算。

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

nothing :: Monad m => MaybeT m a
nothing = MaybeT (return Nothing)

为了使MaybeT 成为 monad 转换器,只要它的参数是 monad,它就必须是 monad。

instance Monad m => Monad (MaybeT m) where
    return = MaybeT . return . Just
    MaybeT m >>= f = MaybeT $ m >>= maybe (return Nothing) (runMaybeT . f)

现在编写MonadTrans 实现。 lift 的实现将基本 monad 的返回值包装在 Just 中。 transform 的实现无趣;它只是告诉 GHC 的约束求解器验证 MaybeT 确实是一个单子,只要它的参数是。

instance MonadTrans MaybeT where
    lift = MaybeT . fmap Just
    transform = Sub Dict

现在我们可以编写一个单子计算,它使用MaybeT 将失败添加到例如State monad。 lift 允许我们使用标准的State 方法getput,但如果我们需要使计算失败,我们也可以访问nothing。 (我考虑过使用您的 IEnumerable(又名 [])的示例,但是将失败添加到已经支持它的 monad 有一些不正当的地方。)

example :: MaybeT (State Int) ()
example = do
    x <- lift get
    if x < 0
    then nothing
    else lift $ put (x - 1)

monad 转换器真正有用的是它们的可堆叠性。这使您可以从许多每个具有一种功能的小 monad 中组合具有许多功能的大 monad。例如,给定的应用程序可能需要做 IO、读取配置变量和抛出异常;这将被编码为类似

的类型
type Application = ExceptT AppError (ReaderT AppConfig IO)

the mtl package 中有一些工具可以帮助您抽象出给定堆栈中 monad 转换器的精确集合和顺序,从而允许您省略对 lift 的调用。

【讨论】:

    【解决方案2】:

    在这种特定情况下,您可以在 MayBe&lt;T&gt; 中实现 IEnumerable&lt;T&gt;,因此它返回 0 或 1 值。

    【讨论】:

      猜你喜欢
      • 2016-03-31
      • 2021-01-04
      • 2017-09-01
      • 2021-10-02
      • 1970-01-01
      • 2011-02-21
      • 1970-01-01
      • 2015-12-11
      • 2015-08-10
      相关资源
      最近更新 更多