Какие монады могут быть выражены как свободные над некоторым функтором?

Документация для Free гласит:

Ряд общих монад возникает как свободные монады,

  • Учитывая data Empty a, Free Empty изоморфен монаде Identity.
  • Свободный Maybe может использоваться для моделирования монархии пристрастности, где каждый уровень представляет выполнение вычисления в течение некоторого времени.

Какие другие монады можно выразить с помощью Free?

Я мог бы думать только об одном: я считаю, что Free (Const e) изоморфно Either e.

Изменить: какие монады не выражаются с помощью Free и почему?

Ответ 1

Почти все из них (до вопросов, связанных с циклом и mfix), но не Cont.

Рассмотрим State monad

newtype State s a = State (s -> (a,s))

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

get :: m s --or equivalently (s -> m a) -> m a
set :: s -> m () --or (s,m a) -> m a
runState :: m a -> s -> (a,s)

мы можем создать свободную монаду с этим интерфейсом, указав операции как конструкторы

data StateF s a
  = Get (s -> a) | Set s a deriving Functor

то имеем

type State s a = Free (StateF s) a

с

get = Impure (Get Pure)
set x = Impure (Set x (Pure ())

и нам просто нужен способ его использования

runState (Pure a) s = (a,s)
runState (Impure (Get f)) s = runState (f s) s
runState (Impure (Set s next)) _ = runState next s

вы можете сделать эту конструкцию с большинством монад. Подобно монадии, возможно,/частичной, определяется

stop :: m a
maybe :: b -> (a -> b) -> m a -> b

это правило, мы рассматриваем каждую из функций, которые заканчиваются на m x для некоторого x как конструктора в функторе, а другие функции - это способы запуска полученной свободной монады. В этом случае

data StopF a = StopF deriving Functor

maybe _ f (Pure a)      = f a
maybe b _ (Impure Stop) = b

почему это круто? Ну, несколько вещей

  • Свободная монада дает вам кусочек данных, который вы можете представить как АСТ для монадического кода. Вы можете писать функции, которые работают с этими данными, что действительно полезно для DSL.
  • Компоненты Functors, что означает разбиение ваших монад, как это делает их полуразлагаемыми. В частности, с учетом двух функторов, которые разделяют алгебру (алгебра является по существу просто функцией f a -> a для некоторого a, когда f является функтором), композиция также имеет эту алгебру.

Состав functor - это просто Мы можем комбинировать функторы несколькими способами, большинство из которых сохраняют эту алгебру. В этом случае мы хотим не состав функторов (f (g (x))), а функторный копродукт. Функторы добавить

data f :+: g a = Inl (f a) | Inr (g a) 

instance (Functor f, Functor g) => Functor (f :+: g) where
  fmap f (Inl x) = Inl (fmap f x)
  fmap f (Inr x) = Inr (fmap f x)

compAlg :: (f a -> a) -> (g a -> a) -> f :+: g a -> a
compAlg f _ (Inl x) = f x
compAlf _ g (Inr x) = g x

также свободные монады сохраняют алгебры

freeAlg :: (f a -> a) -> Free f a -> a
freeAlg _ (Pure a) = a
freeAlg f (Impure x) = f $ fmap (freeAlg f) x

В знаменитой бумаге Wouter Swierstra Типы данных A La Carte это используется для большого эффекта. Простым примером этого документа является калькулятор. Который мы возьмем монадический призыв к новому в этот пост. Для алгебры

class Calculator f where
 eval :: f Integer -> Integer

мы можем думать о различных случаях

data Mult a = Mult a a deriving Functor
instance Calculator Mult where
  eval (Mult a b) = a*b

data Add a = Add a a deriving Functor
instance Calculator Add where
  eval (Add a b) = a+b

data Neg a = Neg a deriving Functor
instance Calculator Neg where
  eval (Neg a) = negate a

instance Calculator (Const Integer) where
  eval (Const a) = a

data Signum a = Signum a deriving Functor
instance Calculator Signum where
  eval (Signum a) = signum a

data Abs a = Abs a deriving Functor
instance Calculator Abs where
  eval (Abs a) = abs a

и наиболее важные

instance (Calculator f, Calculator g) => Calculator (f :+: g) where
   eval = compAlg eval

вы можете определить числовую монаду

newtype Numerical a = Numerical (
        Free (Mult 
        :+: Add 
        :+: Neg 
        :+: Const Integer 
        :+: Signum
        :+: Abs) a deriving (Functor, Monad)

и затем вы можете определить

 instance Num (Numerical a)

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

 class Pretty f where
    pretty :: f String -> String

 instance Pretty Mult where
    pretty (Mult a b) = a ++ "*" ++ b

и аналогичный для всех остальных.

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

Делать это быстро сложно, но у нас есть некоторые трюки. Trick 1 - просто обернуть свою свободную монаду в Codensity (кнопка "идти быстрее" ), но когда это не сработает, вы хотите избавиться от свободного представления. Помните, когда мы

runState (Pure a) s = (a,s)
runState (Impure (Get f)) s = runState (f s) s
runState (Impure (Set s next)) _ = runState next s

Ну, это функция от Free StateF a до s -> (a,s), просто используя тип результата, поскольку наше определение состояния кажется разумным... но как мы определяем операции? В этом случае вы знаете ответ, но одним из способов его получения было бы думать о том, что Коналл Эллиотт называет морфизмами типа класса. Вы хотите

runState (return a) = return a
runState (x >>= f) = (runState x) >>= (runState f)
runState (set x) = set x
runState get = get

что делает его довольно легким

runState (return a) = (Pure a) = \s -> (a,s)

runState (set x) 
   = runState (Impure (Set x (Pure ()))) 
   = \_ -> runState (Pure ()) x
   = \_ -> (\s -> (a,s)) x
   = \_ -> (a,x)

runState get
  = runState (Impure (Get Pure))
  = \s -> runState (Pure s) s
  = \s -> (s,s)

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

Ответ 2

Чтобы ответить на вопрос, как говорится, большинство знакомых монадов, о которых вы не упоминали в вопросе, не являются самими свободными монадами. Ответ Филиппа Дж. Намекает на то, как вы можете связать данную монаду с новой, свободной монадой, но новая монада будет "больше": она имеет более четкие значения, чем оригинальная монада. Например, реальная монада State s удовлетворяет get >>= put = return (), но свободной монады на StateF нет. Как свободная монада, она не удовлетворяет дополнительным уравнениям по определению; это само понятие свободы.

Я покажу, что монады Reader r, Writer w и State s не являются бесплатными, за исключением особых условий на r/w/s.

Позвольте мне ввести некоторую терминологию. Если m является монадой, тогда мы вызываем любое значение типа m a an (m -). Мы называем m-действием тривиальным, если для некоторого x оно равно return x; иначе мы называем это нетривиальным.

Если m = Free f - любая свободная монада на функторе f, то m допускает гомоморфизм монады к Maybe. Это связано с тем, что Free является функториальным в своем аргументе f и Maybe = Free (Const ()), где Const () - конечный объект в категории функторов. Конкретно этот гомоморфизм можно записать

toMaybe :: Free f a -> Maybe a
toMaybe (Pure a) = Just a
toMaybe (Impure _) = Nothing

Поскольку toMaybe является гомоморфизмом монады, он, в частности, удовлетворяет toMaybe (v >> w) = toMaybe v >> toMaybe w для любых m -действий v и w. Теперь заметим, что toMaybe отправляет тривиальные m -действия в тривиальные операции Maybe и нетривиальные m -действия к нетривиальному Maybe -action Nothing. Теперь Maybe обладает тем свойством, что >> с нетривиальным действием с любым действием в любом порядке дает нетривиальное действие (Nothing >> w = v >> Nothing = Nothing); так же верно и для m, так как toMaybe является монодальным монадом, сохраняющим (не) тривиальность.

(Если вы предпочитаете, вы также можете проверить это непосредственно из формулы для >> для бесплатной монады.)

Чтобы показать, что конкретная монада m не изоморфна любой свободной монаде, то достаточно найти m -акций v и w таких, что хотя бы один из v и w нетривиально, но v >> w тривиально.

Reader r удовлетворяет v >> w = w, поэтому нам просто нужно выбрать w = return () и любое нетривиальное действие v, которое существует до тех пор, пока r имеет по крайней мере два значения (тогда ask является непостоянным, т.е., нетривиально).

Для Writer w предположим, что существует обратимый элемент k :: w, отличный от тождественного. Пусть kinv :: w является его обратным. Тогда tell k >> tell kinv = return (), но tell k и tell kinv нетривиальны. Любая нетривиальная группа (например, целые числа при добавлении) имеет такой элемент k. Я полагаю, что свободные монады формы Writer w - это только те, для которых моноид w сам свободен, т.е. w = [a], Writer w ~ Free ((,) a).

При State s аналогично, если s допускает любой нетривиальный автоморфизм f :: s -> s с обратным finv :: s -> s, то modify f >> modify finv = return (). Любой тип s с хотя бы двумя элементами и разрешимым равенством имеет такие автоморфизмы.

Ответ 3

Я написал доказательство того, что монада списка не является бесплатной в размещении в списке рассылки haskell-cafe, на основе данных в ответе Рида:

Напомним, что для любого функтора f мы можем построить свободную монаду над f:

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

Интуитивно, Free f a - тип f-образных деревьев с листьями типа a. Операция объединения просто соединяет деревья и не выполняет никаких действий дальнейшие вычисления. Значения формы (Roll _) называются "нетривиальный" в этой публикации.

Некоторые обычные монады на самом деле являются свободными монадами над некоторым функтором:

  • Монада дерева - это свободная монада над функтором Pair, где data Pair a = MkPair a a. Интуиция в виде парных деревьев является наиболее сильным в этом примере. У каждого родителя node есть пара детей.

  • Возможно, монада - это свободная монада над функтором Unit, где data Unit a = MkUnit. В графическом изображении родительские узлы имеют Unit детей, поэтому детей вообще нет. Таким образом, существует ровно два Unit -образные деревья: дерево, состоящее только из листа (соответствующее до Just _), а три из них состоят из бездетного родителя node (соответствует Nothing).

  • Монада Identity - свободная монада над функтором Void, где Void a - тип без конструкторов.

  • Монада Partiality - свободная монада над Maybe.

  • Монада Writer [r] - свободная монада над функтором (r,). В дереве (r,), родительский node украшен значением типа r и имеет ровно один дочерний элемент node. Прохождение такого пути дает список r и значение листа типа a, поэтому в целом значение типа ([r],a) = Writer r a.

    (В частности, Free (r,) () изоморфно [r]. Это наблюдение привело к утверждению, что монада-список является бесплатной. Но это утверждение неверно и действительно не следует из наблюдения. Чтобы показать, что монада m является свободным, приходится проявлять изоморфизм монады forall a. m a -> Free f a для некоторого функтора f. Напротив, демонстрируя изоморфизм m a с Free (f a) () не является ни достаточным, ни обязательным.)

Еще больше монадов являются факторами свободных монад. Например, State s является фактор Free (StateF s), где

data StateF s a = Put s a | Get (s -> a).
-- NB: This functor is itself a free functor over a type constructor
-- which is sometimes called StateI [1], rendering Free (StateF s) what
-- Oleg and Hiromi Ishii call a "freer monad" [2].

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

run :: Free (StateF s) a -> State s a
run (Pure x)         = return x
run (Roll (Put s m)) = put s >> run m
run (Roll (Get k))   = get >>= run . k

Эта точка зрения является основой операционного пакета apfelmus [1] и также упоминается в потоке StackOverflow [3]. Это главная причина, по которой свободные монады полезны в контексте программирования.

Итак, является ли монада монадой свободной монады? Интуитивно это не так, потому что операция объединения монады списка (конкатенация) не просто трансплантация выражения вместе, но выравнивает их [4].

Вот доказательство того, что монада списка не является бесплатной. Я записываю его с тех пор Я довольно долго интересовался доказательством, но искал это не дало никаких результатов. Однако потоки [3] и [5] приблизились.

В свободной монаде над любым функтором результат связывания нетривиального действие с любой функцией всегда нетривиально, т.е.

(Roll _ >>= _) = Roll _

Это можно проверить непосредственно из определения (>>=) для свободного монада или, альтернативно, с трюком Рейда Бартона об использовании монады морфизм к Возможно, см. [3].

Если монада-список была изоморфной-как-монада свободной монаде над некоторой функтор, изоморфизм будет отображать только одноэлементные списки [x] в значения формы (Pure _) и всех других списков к нетривиальным значениям. Это потому что мономорфизмы монады должны коммутировать с "возвратом" и return x [x] в монаде списка и Pure x в свободной монаде.

Эти два факта противоречат друг другу, как видно из следующего Пример:

do
    b <- [False,True]  -- not of the form (return _)
    if b
        then return 47
        else []
-- The result is the singleton list [47], so of the form (return _).

После применения гипотетического изоморфизма к свободной монаде над некоторым функтора, у нас получилось бы, что привязка нетривиального значения (изображение из [False,True] при изоморфизме) с некоторой функцией приводит к тривиальное значение (изображение [47], т.е. return 47).

Cheers, Инго

[1] http://projects.haskell.org/operational/Documentation.html

[2] http://okmij.org/ftp/Haskell/extensible/more.pdf

[3] Какие монады могут быть выражены как свободные над некоторым функтором?

[4] https://hackage.haskell.org/package/free-4.12.4/docs/Control-Monad-Free.html

[5] https://www.reddit.com/r/haskell/comments/50zvyb/why_is_liststate_not_a_free_monad/

Ответ 4

Все монады могут быть выражены как Свободная Монада. Их дополнительные свойства (например, MonadFix и Cont) могут быть удалены таким образом, потому что Free Monad не является Free MonadFix или Free Cont.

Общий способ - определить functor fmap в терминах liftM, а затем обернуть Free вокруг этого Functor. Полученная Monad может быть сведена к предыдущей/фактической Monad, указав, как функции return и join (Чистые и Нечистые) должны быть сопоставлены с фактической Monad.