阅读上一个SO问题,我很困惑,发现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编写泛型类。问题出在哪里?
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);
}
}
}
为所有monad定义C#接口似乎很容易。哪里出问题了?
您的建议是:
interface IMonad<T>
{
IMonad<T> Wrap(T a);
IMonad<U> Bind<U>(Func<T, IMonad<U>> map);
}
我省略了“展开”,因为提取操作的存在不是monad的要求。(许多monad都有此操作,但并非全部都有。如果需要提取操作,则可能实际上是在使用comonad。)
您问为什么这是错误的。这在几种方面是错误的。
错误的第一种方法是:如果没有实例,就无法通过创建单实例的新Wrap
实例!您在这里遇到了鸡和蛋的问题。
从逻辑上讲,“包装”或“单元”或“返回”操作(无论您想称呼它)都是静态工厂。这就是创建monad的新实例的方式。这不是对实例的操作。它是对类型的静态方法的要求。(或者,要求类型实现特定的构造函数,这实际上是同一件事。无论哪种方式,C#目前均不支持。)
Wrap
接下来,让我们从考虑中消除。为什么Bind
错了?
第二种错误的方法是您没有适当的限制。您的界面说,T的单子是提供绑定操作并返回U的单子的事物。但这还不够严格!假设我们有一个单子Maybe<T> : IMonad<T>
。现在假设我们有以下实现:
class Wrong<T> : IMonad<T>
{
public IMonad<U> Bind<U>(Func<T, IMonad<U>> map)
{
return new Maybe<U>();
}
}
这样就满足了合同,这告诉我们合同不是真正的单子合同。单子合同应该是Wrong<T>.Bind<U>
回报Wrong<U>
,而不是IMonad<U>
!但是我们无法用C#表示“ bind返回定义bind的类的实例”。
同样,这是错误的,因为Func
必须要求调用方提供的而Wrong<U>
不是返回IMonad<U>
。假设我们有第三个monad State<T>
。我们可以有
Wrong<Frog> w = whatever;
var result = w.Bind<Newspaper>(t=>new State<Newspaper>());
现在,这一切都搞砸了。Wrong<T>.Bind<U>
必须具有返回某些函数Wrong<U>
并且本身必须返回Wrong<U>
相同类型的函数,但是此接口允许我们具有一个绑定,该bind接受具有返回State<Newspaper>
但绑定返回的函数Maybe<Newspaper>
。这完全违反了monad模式。您尚未在界面中捕获monad模式。
C#类型系统不够强大,无法表达约束条件“实现方法时,它必须返回执行该实现的类的实例”。如果C#具有“ this_type”编译时注释,则Bind
可以表示为接口,但C#没有该注释。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句