为所有 monad 定义一个 C# 接口似乎很容易。问题出在哪里?
您的建议是:
interface IMonad<T>
{
IMonad<T> Wrap(T a);
IMonad<U> Bind<U>(Func<T, IMonad<U>> map);
}
我省略了“展开”,因为提取操作的存在不是 monad 的要求。 (许多单子都有这个操作,但不是全部都有。如果你require提取操作,您可能实际上正在使用comonad.)
你问为什么这是错误的。这在几个方面都是错误的。
第一种错误是:无法通过以下方式创建 monad 的新实例Wrap
还没有实例!这里有一个先有鸡还是先有蛋的问题。
“wrap”或“unit”或“return”操作——无论你怎么称呼它——在逻辑上是一个静态工厂;它是如何创建 monad 的新实例。这不是对实例的操作。这是类型上静态方法的要求。 (或者,要求类型实现特定的构造函数,这实际上是同一件事。无论哪种方式,目前 C# 都不支持它。)
让我们消除Wrap
从下一点考虑。为什么是Bind
wrong?
第二种错误是你没有适当的限制。你的接口说 T 的 monad 是一个提供返回 U 的 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>
回报Wrong<U>
, not IMonad<U>
!但我们无法在 C# 中表达“bind 返回定义 bind 的类的实例”。
同样,这是错误的,因为Func
必须要求调用者返回Wrong<U>
, not IMonad<U>
。假设我们有第三个单子,比如说,State<T>
。我们可以有
Wrong<Frog> w = whatever;
var result = w.Bind<Newspaper>(t=>new State<Newspaper>());
现在一切都乱了。Wrong<T>.Bind<U>
必须采用一个返回一些值的函数Wrong<U>
并且必须自己返回Wrong<U>
相同类型,但是这个接口允许我们有一个绑定,它接受一个返回的函数State<Newspaper>
但绑定返回Maybe<Newspaper>
。这完全违反了 monad 模式。您尚未在界面中捕获 monad 模式。
C# 类型系统不够强大,无法表达“当实现该方法时,它必须返回执行该实现的类的实例”的约束。如果 C# 有一个“this_type”编译时注释,那么Bind
可以表示为接口,但 C# 没有该注释。