Объединение монад (например, IEnumerable и Maybe) - PullRequest
8 голосов
/ 10 ноября 2011

У меня есть общий вопрос и более конкретный кейс-вопрос.

Как объединить разные монады в целом?Позволяет ли некоторая комбинация операторов монады легко составлять?Или нужно написать специальные методы для объединения каждой возможной пары монад?

В качестве конкретного примера я написал монаду Maybe.Как можно использовать IEnumerable<IMaybe<T>>?Помимо ручного поиска в монаде Maybe в расширениях LINQ (например, if(maybe.HasValue) ... в предложениях select), существует ли «монадный» способ объединения двух с соответствующими операциями монады Bind и т. Д.?

В противном случае, если мне нужно написать конкретные методы объединения, будет ли что-то вроде этого правильным способом?

    public static IEnumerable<B> SelectMany<A, B>(this IEnumerable<A> sequence, Func<A, IMaybe<B>> func)
    {
        return from item in sequence
               let result = func(item)
               where result.HasValue
               select result.Value;
    }


    public static IEnumerable<C> SelectMany<A, B, C>(this IEnumerable<A> sequence, Func<A, IMaybe<B>> func, Func<A, B, C> selector)
    {
        return from item in sequence
               let value = item
               let maybe = func(item)
               where maybe.HasValue
               select selector(value, maybe.Value);
    }

Ответы [ 2 ]

0 голосов
/ 02 декабря 2016

Отличный вопрос!

Преобразователь монад - это тип, который добавляет некоторые функциональные возможности к произвольной базовой монаде, сохраняя при этом монадность.К сожалению, монадные преобразователи невыразимы в C #, потому что они существенно используют типы более высокого класса.Итак, работая в Haskell,

class MonadTrans (t :: (* -> *) -> (* -> *)) where
    lift :: Monad m => m a -> t m a
    transform :: Monad m :- Monad (t m)

Давайте пройдемся по этой строке за строкой.В первой строке объявляется, что преобразователь монад представляет собой тип t, который принимает аргумент вида * -> * (то есть тип, ожидающий один аргумент) и превращает его в другой тип * -> *.Когда вы понимаете, что все монады имеют вид * -> *, вы можете видеть, что намерение состоит в том, что t превращает монады в другие монады.

Следующая строка говорит, что все преобразователи монад должны поддерживать операцию lift, которая берет произвольную монаду m и поднимает ее в мир трансформатора t m.

Наконец, метод transform говорит, что для любой монады m, t m также должна быть монадой.Я использую оператор entailment :- из пакет constraints .


Это станет более понятным на примере.Вот преобразователь монад, который добавляет Maybe -ность к произвольной базовой монаде m.Оператор nothing позволяет нам прервать вычисление.

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

nothing :: Monad m => MaybeT m a
nothing = MaybeT (return Nothing)

Чтобы MaybeT был преобразователем монад, он должен быть монадой всякий раз, когда его аргумент является монадой.

instance Monad m => Monad (MaybeT m) where
    return = MaybeT . return . Just
    MaybeT m >>= f = MaybeT $ m >>= maybe (return Nothing) (runMaybeT . f)

Теперь, чтобы написать реализацию MonadTrans.Реализация lift оборачивает возвращаемое значение базовой монады в Just.Реализация transform неинтересна;он просто говорит решателю ограничений GHC проверять, что MaybeT действительно является монадой, когда ее аргумент равен.

instance MonadTrans MaybeT where
    lift = MaybeT . fmap Just
    transform = Sub Dict

Теперь мы можем написать монадическое вычисление, которое использует MaybeT, чтобы добавить сбой, например,State монада.lift позволяет нам использовать стандартные State методы get и put, но у нас также есть доступ к nothing, если нам нужно потерпеть неудачу в вычислениях.(Я думал об использовании вашего примера IEnumerable (он же []), но есть кое-что извращенное в добавлении сбоя в монаду, которая уже его поддерживает.)

example :: MaybeT (State Int) ()
example = do
    x <- lift get
    if x < 0
    then nothing
    else lift $ put (x - 1)

Что делает монадные преобразователи действительно полезными,их наращиваемость.Это позволяет вам составлять большие монады с множеством возможностей из множества маленьких монад с одной способностью каждая.Например, конкретному приложению может потребоваться выполнить ввод-вывод, прочитать переменные конфигурации и выбросить исключения;это будет закодировано с помощью типа

type Application = ExceptT AppError (ReaderT AppConfig IO)

В mtl пакете есть инструменты, которые помогут вам абстрагироваться от точного набора и порядка монадных преобразователей в данном стеке, позволяя вам отклонять вызовы на lift.

0 голосов
/ 11 марта 2012

В этом конкретном случае вы можете реализовать IEnumerable<T> в MayBe<T>, поэтому он возвращает либо значение 0, либо 1.

...