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

ano*_*ous 5 c# generics monads interface

阅读Previous SO Question我很困惑地发现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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 没有“更高级的类型”=> 使用父接口
  • 接口中没有静态方法。=> 为什么使用静态?!只使用实例方法

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

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); 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 10

为所有 monad 定义 C# 接口似乎很容易。问题出在哪里?

你的提议是:

interface IMonad<T>
{
    IMonad<T> Wrap(T a);
    IMonad<U> Bind<U>(Func<T, IMonad<U>> map);
}
Run Code Online (Sandbox Code Playgroud)

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

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

第一个错误的方式是:如果没有实例,就无法通过 via 创建新的 monadWrap实例!你在这里有一个鸡和蛋的问题。

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

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

第二种错误方式是您没有适当的限制。您的接口说 T 的 monad 是一种提供绑定操作的东西,该操作返回 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>();
  }
}
Run Code Online (Sandbox Code Playgroud)

这满足合约,它告诉我们合约不是真正的 monad 合约。monad 契约应该是Wrong<T>.Bind<U>返回Wrong<U>,而不是IMonad<U>!但是我们无法在 C# 中表达“绑定返回定义绑定的类的实例”。

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

Wrong<Frog> w = whatever;
var result = w.Bind<Newspaper>(t=>new State<Newspaper>());
Run Code Online (Sandbox Code Playgroud)

而现在这一切都一团糟。 Wrong<T>.Bind<U>必须接受一个返回 some 的函数,Wrong<U>并且必须本身返回Wrong<U>相同类型,但是这个接口允许我们有一个绑定,它接受一个返回State<Newspaper>但绑定返回的函数Maybe<Newspaper>。这完全违反了 monad 模式。您尚未在界面中捕获 monad 模式。

C# 类型系统不够强大,无法表达“在实现方法时,它必须返回执行该实现的类的实例”的约束。如果 C# 有“this_type”编译时注释,则Bind可以表示为接口,但 C# 没有该注释。

  • 哇!不敢相信你自己回答了我:) 谢谢!现在我终于明白了。非常感谢您花时间详细解释。 (2认同)