C#中的Monads - 为什么Bind实现需要传递函数来返回monad?

And*_*kin 17 c# monads

我在C#中看到的monad的大多数例子都是这样编写的:

public static Identity<B> Bind<A, B>(this Identity<A> a, Func<A, Identity<B>> func) {
    return func(a.Value);
}
Run Code Online (Sandbox Code Playgroud)

例如,请参阅http://mikehadlow.blogspot.com/2011/01/monads-in-c-3-creating-our-first-monad.html.

问题是,要求func退货的重点是Identity<B>什么?如果我使用以下定义:

public interface IValue<A> {
    public IValue<B> Bind<B>(Func<A, B> func)
}
Run Code Online (Sandbox Code Playgroud)

那么我可以实际使用的同funcLazy<T>,Task<T>,Maybe<T>等没有实际根据实际类型实现IValue.

我在这里缺少什么重要的东西?

Eri*_*ert 33

首先,考虑一下构图的概念.我们可以轻松地将组合表达为对代表的操作:

public static Func<T, V> Compose<T, U, V>(this Func<U, V> f, Func<T, U> g)
{
    return x => f(g(x));
}
Run Code Online (Sandbox Code Playgroud)

因此,如果我有一个函数g,它是(int x) => x.ToString()函数f,(string s) => s.Length那么我就可以(int x) => x.ToString().Length通过调用来创建一个函数h f.Compose(g).

这应该是清楚的.

现在假设我有从函数g T,以Monad<U>和从函数f UMonad<V>.我想编写一个组成这两个函数返回单子成需要的功能的方法T,并返回Monad<V>.所以我试着写下:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, Monad<V>> f, Func<T, Monad<U>> g)
{
    return x => f(g(x));
}
Run Code Online (Sandbox Code Playgroud)

不行.g返回一个Monad<U>但是f需要一个U.我有一个方法来"包装"一UMonad<U>,但我没有办法"解包"一个.

但是,如果我有一个方法

public static Monad<V> Bind<U, V>(this Monad<U> m, Func<U, Monad<V>> k)
{ whatever }
Run Code Online (Sandbox Code Playgroud)

然后我可以编写一个方法来组合两个返回monad的方法:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, Monad<V>> f, Func<T, Monad<U>> g)
{
    return x => Bind(g(x), f);
}
Run Code Online (Sandbox Code Playgroud)

这就是为什么需要绑定一个FUNC自TMonad<U>-因为事情的全部意义在于能够利用从函数g TMonad<U>和一个函数f从UMonad<V>并撰写成函数h从TMonad<V>.

如果你想利用从函数g TU和一个函数f从UMonad<V>那么你不首先需要绑定.刚刚撰写的功能通常从拿到方法TMonad<V>!Bind的全部目的是解决这个问题; 如果你把这个问题挥了出去,那么你首先不需要Bind.

更新:

在大多数情况下,我想从构成函数g TMonad<U>和函数f从UV.

而且我相信,你想,编写成函数从TV.但是你不能保证这样的操作是定义的!例如,将"Maybe monad"作为monad,用C#表示T?.假设你有g as (int x)=>(double?)null,你有一个函数f (double y)=>(decimal)y.你应该如何将f和g组合成一个接受int并返回非可空decimal类型的方法?没有"展开"将可空双重打包成f可以采用的双重值!

您可以使用Bind将f和g组合成一个接受int并返回可为空的十进制的方法:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, V> f, Func<T, Monad<U>> g)
{
    return x => Bind(g(x), x=>Unit(f(x)));
}
Run Code Online (Sandbox Code Playgroud)

其中Unit是一个带a V并返回a 的函数Monad<V>.

但是如果g返回monad并且f不返回monad,则根本没有f和g的组合 - 不能保证有一种方法可以从monad的实例返回到"unwrap"类型.也许在一些monad的情况下总是有 - 像Lazy<T>.或者也许有时候,就像"也许"monad一样.通常有一种方法可以做到,但没有要求你可以这样做.

顺便提一下,请注意我们如何使用"Bind"作为瑞士军刀来制作新的作品.绑定可以进行任何操作!例如,假设我们在序列monad上有Bind操作,我们IEnumerable<T>在C#中的类型上称为"SelectMany" :

static IEnumerable<V> SelectMany<U, V>(this IEnumerable<U> sequence, Func<U, IEnumerable<V>> f)
{
    foreach(U u in sequence)
        foreach(V v in f(u))
            yield return v;
}
Run Code Online (Sandbox Code Playgroud)

您可能还有一个序列运算符:

static IEnumerable<A> Where<A>(this IEnumerable<A> sequence, Func<A, bool> predicate)
{
    foreach(A item in sequence)
        if (predicate(item)) 
            yield return item;
}
Run Code Online (Sandbox Code Playgroud)

你真的需要在里面写这些代码Where吗?没有!你可以完全用"Bind/SelectMany"构建它:

static IEnumerable<A> Where<A>(this IEnumerable<A> sequence, Func<A, bool> predicate)
{
    return sequence.SelectMany((A a)=>predicate(a) ? new A[] { a } : new A[] { } );  
}
Run Code Online (Sandbox Code Playgroud)

高效?不,但Bind/SelectMany没有什么不能做的.如果你真的想要你可以构建除了SelectMany之外的所有LINQ序列运算符.