кажется, что легко определить интерфейс C# для всех монад. В чем проблема?
Ваше предложение:
interface IMonad<T>
{
IMonad<T> Wrap(T a);
IMonad<U> Bind<U>(Func<T, IMonad<U>> map);
}
Я опустил "развернуть", потому что существование операции извлечения не является требованием монады. (Многие монады выполняют эту операцию, но не все делают. Если вам требуется операция извлечения, вы, вероятно, фактически используете comonad .)
Вы спрашиваете, почему это неправильно. Это неправильно по нескольким причинам.
Первый способ неверен: нет способа создать новый экземпляр монады через Wrap
, не имея экземпляра! У вас есть проблема курицы и яйца здесь.
Операция «обертка», «единица» или «возврат» - как бы вы ее ни называли - логически является фабрикой c; Это , как вы делаете новый экземпляр монады . Это не операция над экземпляром. Это требование метода stati c для типа. (Или требование, чтобы тип реализовывал конкретный конструктор, который фактически является тем же самым. В любом случае, в настоящее время он не поддерживается в 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>
. Предположим, у нас есть третья монада, скажем, 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>
, Это полное нарушение паттерна монады. Вы не захватили образец монады в своем интерфейсе.
Система типов C# недостаточно сильна, чтобы express ограничение ", когда метод реализован, он должен возвратить экземпляр класса, который сделал реализация". Если C# имеет аннотацию "this_type" во время компиляции, тогда Bind
может быть выражено как интерфейс, но C# не имеет этой аннотации.