Подтвердить что ты не робот

Как обнаружить Монаду?

У многих из нас нет фона по функциональному программированию и, тем более, на алгебре теории категорий. Поэтому давайте предположим, что нам нужно и, следовательно, создать общий тип типа

data MySomething t = ....... 

Затем мы продолжаем программирование и используя MySomething. Какие доказательства должны предупредить нас о том, что MySomething является монадой, и мы должны сделать ее одной, написав instance Monad MySomething ... и определяя для нее return и >>=?

Спасибо.

Изменить: См. также этот вопрос: является цепной операцией единственное, что решает вопрос монада?, и этот ответ монада - это массив функций с вспомогательными операциями

4b9b3361

Ответ 1

Большим шагом вперед в моем понимании монад была публикация Монады - Деревья с прививкой. Если ваш тип выглядит как дерево, а значения t появляются на листьях, то у вас на руках может быть монада.

Простые примеры

Некоторые типы данных, очевидно, являются деревьями, например, тип Maybe

data Maybe a = Nothing | Just a

который либо имеет нулевой лист, либо лист с одним значением. Список является еще одним очевидным типом дерева

data List a = Nil | Cons a (List a)

который является либо нулевым листом, либо листом с одним значением и другим списком. Еще более очевидное дерево - это двоичное дерево

data Tree a = Leaf a | Bin (Tree a) (Tree a)

со значениями на листьях.

Сложные примеры

Однако некоторые виды на первый взгляд не похожи на деревья. Например, монада 'reader' (она же монада функции или окружающая среда) выглядит как

data Reader r a = Reader { runReader :: r -> a }

В данный момент не похоже на дерево. Но давайте специализируемся на конкретном типе r, например Bool -

data ReaderBool a = ReaderBool (Bool -> a)

Функция от Bool до a эквивалентна паре (a,a), где левый элемент пары - это значение функции в True, а правый аргумент - значение в False - -

data ReaderBool a = ReaderBool a a

который больше похож на дерево с одним типом листа - и действительно, вы можете превратить его в монаду

instance Monad ReaderBool where
    return a = ReaderBool a a
    ReaderBool a b >>= f = ReaderBool a' b'
      where
        ReaderBool a' _ = f a
        ReaderBool _ b' = f b

Мораль заключается в том, что функцию r -> a можно рассматривать как большой длинный кортеж, содержащий множество значений типа a, по одному для каждого возможного ввода, - и этот кортеж можно рассматривать как лист особенно простого дерева.

Государственная монада является еще одним примером этого типа

data State s a = State { runState :: s -> (a, s) }

где вы можете просмотреть s -> (a, s) как большой набор значений типа (a, s) - один для возможного ввода типа s.

Еще один пример - упрощенная монада действий ввода-вывода

data Action a = Put String (Action a)
              | Get (String -> Action a)
              | Return a

Это дерево с тремя типами листьев - лист Put, который просто выполняет другое действие, лист Get, который можно рассматривать как бесконечный набор действий (по одному на каждый возможный вход String). ) и простой лист Return, который содержит только одно значение типа a. Таким образом, похоже, что это может быть монадой, и на самом деле это

instance Monad Action where
  return = Return

  Put s a  >>= f = Put s (a >>= f)
  Get g    >>= f = Get (\s -> g s >>= f)
  Return a >>= f = f a

Надеюсь, это дало вам немного интуиции.

Думая о монадах как о деревьях, операция return как способ получения простого дерева с одним значением и операция >>= как способ замены элементов на листьях дерева новыми деревьями могут стать мощным объединением способ смотреть на монады.

Ответ 2

Стоит упомянуть, что нет прямого способа заметить, что что-то есть Монада, вместо этого это процесс, который вы проходите, когда подозреваете, что что-то может быть Монадой, чтобы доказать, что ваше подозрение верное.

Тем не менее, есть способы улучшить вашу чувствительность к Monads.

Знать зависимости

Для любого типа T закономерное instance Monad T означает, что существует закономерное instance Applicative T и законопослушное instance Functor T. Часто Functor легче обнаружить (или опровергнуть), чем Monad. Некоторые вычисления могут быть легко обнаружены их структурой Applicative, прежде чем увидеть, что они также являются Monad.

Для конкретности, здесь, как вы доказываете, что любой Monad является Functor и Applicative

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype Wrapped m a = W { unW :: m a } -- newtype lets us write new instances
  deriving ( Monad )

instance Monad m => Functor (Wrapped m) where
  fmap f (W ma) = W (ma >>= return . f)

instance Monad m => Applicative (Wrapped m) where
  pure = W . return
  W mf <*> W mx = W $ do
    f <- mf
    x <- mx
    return (f x)

Как правило, лучшим ресурсом, доступным для понимания этой иерархии типов, является Typeclassopedia. Я не могу рекомендовать прочитать его достаточно.

Знайте свои стандартные монады и трансформаторы

Там довольно стандартный набор простых монадов, с которыми должен быть сразу знаком любой промежуточный программист Haskell. Это Writer, Reader, State, Identity, Maybe и Either, Cont и []. Часто вы обнаружите, что ваш тип - это всего лишь небольшая модификация одной из этих стандартных монадов и, таким образом, может быть сделана сама монада способом, подобным стандарту.

Далее, некоторые Monad s, называемые трансформаторами, "стек" для формирования других Monad s. Это конкретно означает, что вы можете объединить (модифицированную форму) монаду Reader и монаду Writer, чтобы сформировать монаду ReaderWriter. Эти модифицированные формы отображаются в пакетах transformers и mtl и обычно демаркируются добавленным T. Конкретно, вы можете определить ReaderWriter с помощью стандартных трансформаторов из transformers, как это показано

import Control.Monad.Trans.Reader
import Control.Monad.Writer

newtype ReaderWriter r w a = RW { unRW :: ReaderT r (Writer w) a }
  deriving Monad

-- Control.Monad.Trans.Reader defines ReaderT as follows
--
--     newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
--
-- the `m` is the "next" monad on the transformer stack

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

Чтобы узнать их, лучше всего изучить модули в пакетах transformers и mtl.

Следите за секвенированием

Монады часто вводятся для обеспечения явного последовательности действий. Если вы пишете тип, который требует конкретного представления последовательности действий, у вас может быть монада на руках, но вы также можете просто иметь Monoid.

См. предыдущий мой ответ для довольно подробного обсуждения того, как определенная последовательность может быть записана как Monad... но не получила никакого преимущества от этого.. Иногда последовательность - это всего лишь список.

Знать расширения

Иногда у вас будет тип данных, который явно не является монадой, но, очевидно, что-то зависит от экземпляра монады. Обычный пример - синтаксический анализ, где может быть очевидно, что вам нужно иметь поиск, который следует за множеством альтернатив, но не сразу ясно, что вы можете сформировать монаду из этого.

Но если вы знакомы с Applicative или Monad, вы знаете, что существуют классы Alternative и MonadPlus

instance Monad m => MonadPlus m where ...
instance Applicative f => Alternative f where ...

которые полезны для структурных вычислений, которые берут альтернативы. Это говорит о том, что, возможно, существует способ найти структуру монады в вашем типе!

Знайте свободную структуру

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

Свободная монада определяется следующим образом

data Free f a = Pure a
              | Fix (f (Fix f a))

который является только фиксированной точкой нашего функтора f, примыкающего к значению Pure. Если вы изучаете фиксированные точки типа (recursion-schemes или Bartosz Milewski Понимание F-алгебр для более) вы обнаружите, что бит Fix просто определяет любой рекурсивный тип data, а бит Pure позволяет нам вводить "дыры" в этот обычный тип, который заполняется на a s.

(>>=) для Free Monad - это просто взять один из этих a и заполнить его отверстие новым Free f a.

(>>=) :: Free f a -> (a -> Free f a) -> Free f a
Pure a >>= g = g a
Fix fx >>= g = Fix (fmap (>>= g) fx) -- push the bind down the type

Это понятие очень похоже на ответ Криса Тейлора --- Монады - это только древовидные типы, где (>>=) прививает новые древовидные части, где раньше были листья. Или, как я описал выше, Монады являются обычными типами с отверстиями Pure, которые могут быть заполнены позже.

Свободные монады имеют гораздо большую глубину в своей абстрактности, поэтому я бы порекомендовал Gabriel Gonzalez Очистите свой код со свободной монадой, в которой показано вы, как моделировать сложные вычисления, используя свободные монады.

Знать канонические разложения

Последний трюк, который я собираюсь предложить, объединяет понятие свободной монады и понятие секвенирования и является основой для новых родовых пакетов монады, таких как extensible-effects.

Один способ думать о монадах - это набор инструкций, выполняемых последовательно. Например, монада State может быть инструкцией

Get :: State s s
Put :: s -> State s ()

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

data StateF s x = Get (s -> x) | Put s x deriving Functor

Причина, по которой мы вводим параметр x, состоит в том, что мы будем последовательно выполнять операции StateF, формируя фиксированную точку StateF. Интуитивно это так, как если бы мы заменили его x на StateF, чтобы мы могли написать такой тип, как

modify f = Get (\s -> Put (f s) (...))

где (...) - следующее действие в последовательности. Вместо продолжения этого навсегда мы используем конструктор Pure из монады Free выше. Для этого мы также должны пометить биты не Pure Fix

-- real Haskell now
modify f = Fix (Get $ \s -> Fix (Put (f s) (Pure ()))

Этот способ мышления развивается намного дальше, и я снова направлю вас на статью Габриэля.

Но то, что вы можете убрать прямо сейчас, это то, что иногда у вас есть тип, который указывает последовательность событий. Это можно интерпретировать как определенный вид канонического способа представления Monad, и вы можете использовать Free для построения рассматриваемой Монады из вашего канонического представления. Я часто использую этот метод для создания "семантических" монадов в моих приложениях, таких как "монада доступа к базе данных" или "монашеская" монада.

Ответ 3

По моему опыту, самый простой способ узнать и одновременно построить интуицию для монад - просто попытаться реализовать return и (>>=) для вашего типа и убедиться, что они удовлетворяют законам монады.

Ответ 4

Вы должны быть в поиске, если вы в конце концов напишите какие-либо операции с подписями, которые выглядят как какие-либо из них, или, что еще важнее, если у вас есть несколько функций, которые можно реорганизовать для их использования:

----- Functor -----

-- Apply a one-place function "inside" `MySomething`.
fmap :: (a -> b) -> MySomething a -> MySomething b

----- Applicative -----

-- Apply an n-place function to the appropriate number and types of 
-- `MySomething`s:
lift :: (a -> ... -> z) -> MySomething a -> ... -> MySomething z

-- Combine multiple `MySomething`s into just one that contains the data of all
-- them, combined in some way.  It doesn't need to be a pair—it could be any 
-- record type.
pair :: MySomething a -> ... -> MySomething z -> MySomething (a, ..., z)

-- Some of your things act like functions, and others like their arguments.
apply :: MySomething (a -> b) -> MySomething a -> MySomething b

-- You can turn any unadorned value into a `MySomething` parametrized by
-- the type
pure :: a -> MySomething a

-- There is some "basic" constant MySomething out of which you can build 
-- any other one using `fmap`.
unit :: MySomething ()

----- Monad -----

bind :: MySomething a -> (a -> MySomething b) -> MySomething b
join :: MySomething (MySomething a) -> MySomething a

----- Traversable -----

traverse :: Applicative f => (a -> f b) -> MySomething a -> f (MySomething b)
sequence :: Applicative f => MySomething (f a) -> f (MySomething a)

Обратите внимание на четыре вещи:

  • Applicative может быть менее известным, чем Monad, но это очень важный и ценный класс - возможно, центральная часть API! Множество вещей, из которых первоначально использовались люди Monad, на самом деле требуют только Applicative. Хорошая практика не использовать Monad, если Applicative будет делать.
  • Подобные замечания могут быть сделаны из Traversable - множество функций, которые изначально были написаны для Monad (sequence, mapM), на самом деле требуют только Traversable + Applicative.
  • Как следствие выше, часто то, как вы что-то обнаруживаете, - это Monad, сначала обнаружив, что это a Applicative, а затем спрашивает, не является ли оно также Monad.
  • Не забывайте о законах - они авторитетный арбитр того, что делает его, а что нет.