Вот монада C#, в чем проблема? - PullRequest
4 голосов
/ 26 января 2020

Чтение Предыдущий вопрос SO Я был озадачен, обнаружив Eri c Lippert , говоря, что интерфейс не может быть определен в C# для всех монад, используя реализацию, как показано ниже :

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

Моя проблема в том, что все проблемы, перечисленные в вопросе, кажутся простыми:

  • нет "типов с более высоким родом" => использовать родительские интерфейсы
  • нет stati c метод в интерфейсе. => зачем использовать stati c ?! просто используйте методы экземпляра

Monad - это шаблон, позволяющий объединять операции над упакованными типами. Кажется, легко определить интерфейс C# для всех монад, позволяющий нам написать обобщенный класс c для всех монад. Где проблема?

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); 
        }
    }
}

1 Ответ

9 голосов
/ 28 января 2020

кажется, что легко определить интерфейс 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# не имеет этой аннотации.

...