组合monads(以IEnumerable和Maybe为例)

时间:2011-11-10 08:21:52

标签: c# ienumerable monads

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

一般如何组合不同的monad? monad运算符的某些组合是否允许轻松组合?或者是否必须编写特殊方法来组合每对可能的monad?

作为一个具体的例子,我写了一个Maybe monad。如何使用IEnumerable<IMaybe<T>>?除了手动挖掘到LINQ扩展中的Maybe monad(例如:if(maybe.HasValue) ... select子句中)之外,是否存在将两者与其各自的Bind等组合的“monadic”方式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);
    }

2 个答案:

答案 0 :(得分:0)

很棒的问题!

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,它接受​​类* -> *的参数(即,期望一个参数的类型)并将其转换为另一种类型{{1} }。当你意识到所有monad都有类* -> *时,你可以看到* -> *将monad变成其他monad的意图。

下一行说明所有monad变换器都必须支持t操作,该操作需要一个任意monad lift提升<变速器进入变换器的世界m

最后,t m方法表示对于任何monad transformm也必须是monad。我正在使用the constraints package中的蕴涵运算符t m

通过一个例子,这将更有意义。这是一个monad变换器,它将:- - 添加到任意基础monad Maybem运算符允许我们中止计算。

nothing

为了使newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } nothing :: Monad m => MaybeT m a nothing = MaybeT (return Nothing) 成为monad变换器,只要它的参数是monad,它就必须是monad。

MaybeT

现在编写instance Monad m => Monad (MaybeT m) where return = MaybeT . return . Just MaybeT m >>= f = MaybeT $ m >>= maybe (return Nothing) (runMaybeT . f) 实现。 MonadTrans的实现将基本monad的返回值包装在lift中。 Just的实施是无趣的;它只是告诉GHC的约束求解器,只要它的参数是transform就确实是一个monad。

MaybeT

现在我们可以编写一个monadic计算,使用instance MonadTrans MaybeT where lift = MaybeT . fmap Just transform = Sub Dict 来添加失败,例如MaybeT monad。 State允许我们使用标准的lift方法Stateget,但如果我们需要使计算失败,我们也可以访问put。 (我考虑使用你的nothing(又名IEnumerable)的例子,但是对于已经支持它的monad添加失败有一些不妥之处。)

[]

使monad变换器真正有用的是它们的可堆叠性。这允许你用许多小monad组成具有许多功能的大monad,每个monad具有一个功能。例如,给定的应用程序可能需要执行IO,读取配置变量并抛出异常;这将使用类似

的类型进行编码
example :: MaybeT (State Int) ()
example = do
    x <- lift get
    if x < 0
    then nothing
    else lift $ put (x - 1)

the mtl package中有一些工具可以帮助您抽象出给定堆栈中monad变换器的精确集合和顺序,从而允许您忽略对type Application = ExceptT AppError (ReaderT AppConfig IO) 的调用。

答案 1 :(得分:-1)

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