这是 C# Monad,问题出在哪里?

Here is the C# Monad, where is the problem?

阅读 I was confused to find Eric Lippert 说不能在 C# 中为所有 Monad 定义接口,使用如下实现:

typeInterface Monad<MonadType<A>>
{
       static MonadType<A> Return(A a);
       static MonadType<B> Bind<B>(MonadType<A> x, Func<A, MonadType<B>> f);
}

我的问题是问题中列出的所有问题似乎都有简单的解决方案:

Monad 是一种允许对包装类型进行操作链接的模式 为所有 Monad 定义一个 C# 接口似乎很容易,允许我们为所有 Monad 编写一个通用的 class 问题出在哪里?

using System;
using System.Linq;          
public class Program
{
    public static void Main()
    {//it works, where's the problem?
            new SequenceMonad<int>(5)
                .Bind(x => new SequenceMonad<float>(x + 7F))
                .Bind(x => new SequenceMonad<double>(x + 5D))
                ;
    }
    interface IMonad<T>{

        IMonad<T> Wrap(T a);
        IMonad<U> Bind<U>(Func<T, IMonad<U>> map);
        T UnWrap();//if we can wrap we should be able to unwrap
    }
    class GenericClassForAllMonads<T>
    {//example writing logic for all monads
        IMonad<U> DoStuff<U>(IMonad<T> input, Func<T, IMonad<U>> map)
        { return map(input.UnWrap()); }
    }
    class SequenceMonad<T> : IMonad<T> where T:new()
    {//specific monad implementation
        readonly T[] items;//immutable
        public SequenceMonad(T a)
        {
            Console.WriteLine("wrapped:"+a);
            items =  new[] { a }; 
        }
        public IMonad<B> Bind<B>(Func<T, IMonad<B>> map)
        {  return map(UnWrap()); }

        public T UnWrap()
        { return items == null? default(T) : items.FirstOrDefault();  }

        public IMonad<T> Wrap(T a)
        {
            Console.WriteLine("wrapped:"+a);
            return new SequenceMonad<T>(a); 
        }
    }
}

it seems easy to define a C# interface for all monads. Where's the problem?

您的建议是:

interface IMonad<T>
{
    IMonad<T> Wrap(T a);
    IMonad<U> Bind<U>(Func<T, IMonad<U>> map);
}

我省略了 "unwrap" 因为提取操作的存在不是 monad 的要求。 (许多 monad 都有这个操作,但不是全部都有。如果你 需要 一个提取操作,你可能实际上正在使用 comonad。)

你问为什么这是错误的。这在几个方面是错误的。

错误的第一种方式是:没有实例就无法通过 Wrap 创建 monad 的新实例!你在这里遇到了先有鸡还是先有蛋的问题。

"wrap" 或 "unit" 或 "return" 操作——不管你怎么称呼它——在逻辑上是一个静态工厂; 如何创建 monad 的新实例。这不是对实例的操作。这是对类型的静态方法的要求。 (或者,类型实现特定构造函数的要求,这实际上是同一件事。无论哪种方式,目前 C# 都不支持。)

让我们在下一点的考虑中排除 Wrap。为什么 Bind 错误?

第二种错误的方式是你没有适当的限制。您的界面说 T 的 monad 是提供绑定操作的东西 return 是 U 的 monad。但这还不够严格!假设我们有一个 monad Maybe<T> : IMonad<T>。现在假设我们有这个实现:

class Wrong<T> : IMonad<T>
{
  public IMonad<U> Bind<U>(Func<T, IMonad<U>> map)
  {
    return new Maybe<U>();
  }
}

满足契约,这告诉我们契约不是真正的monad契约。 monad契约应该是Wrong<T>.Bind<U> returns Wrong<U>,而不是IMonad<U>!但是我们在C#中没有办法表达"bind returns an instance of the class which defines bind".

同样是错误的,因为调用者提供的Func必须是returnWrong<U>,而不是IMonad<U>。假设我们有第三个 monad,比如说 State<T>。我们可以

Wrong<Frog> w = whatever;
var result = w.Bind<Newspaper>(t=>new State<Newspaper>());

现在这一切都搞砸了。 Wrong<T>.Bind<U> 必须采用 return 一些 Wrong<U> 的函数,并且它本身必须 return Wrong<U> 属于同一类型,但是这个接口允许我们有一个绑定采用 returns State<Newspaper> 但绑定 returns Maybe<Newspaper> 的函数。这完全违反了 monad 模式。您还没有在界面中捕获 monad 模式。

C# 类型系统不够强大,无法表达约束 "when the method is implemented it must return an instance of the class that did the implementation"。如果 C# 有一个 "this_type" 编译时注释,那么 Bind 可以表示为一个接口,但 C# 没有那个注释。