Что такое бесплатные монады?

369

Я видел этот термин Free Монада всплывал каждый в настоящее время , и затем в течение некоторого времени, но каждый раз кажется , что использование / обсуждать их , не давая объяснение того , что они есть. Итак: что такое бесплатные монады? (Я бы сказал, что я знаком с монадами и основами Хаскелла, но имею только очень грубые знания теории категорий.)

Дэвид
источник
12
Довольно хорошее объяснение здесь haskellforall.com/2012/06/…
Роджер Линдсё
19
@ Роджер, это своего рода страница, которая привела меня сюда. Для меня этот пример определяет экземпляр монады для типа с именем «Free» и все.
Дэвид

Ответы:

296

Ответ Эдварда Кметта, очевидно, великолепен. Но это немного технический. Вот, пожалуй, более доступное объяснение.

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

liftFree :: Functor f => f a -> Free f a
foldFree :: Functor f => (f r -> r) -> Free f r -> r

первый из них позволяет вам «войти» в вашу монаду, а второй - как «выйти» из нее.

В более общем смысле, если X - это Y с некоторыми дополнительными элементами P, то «свободный X» - это способ перехода от Y к X без получения чего-либо дополнительного.

Примеры: моноид (X) - это набор (Y) с дополнительной структурой (P), который в основном говорит, что у него есть операция (вы можете думать о сложении) и некоторая идентичность (например, ноль).

Так

class Monoid m where
   mempty  :: m
   mappend :: m -> m -> m

Теперь мы все знаем списки

data [a] = [] | a : [a]

Ну, учитывая любой тип, tмы знаем, что [t]это моноид

instance Monoid [t] where
  mempty   = []
  mappend = (++)

и поэтому списки являются «свободным моноидом» над наборами (или в типах Haskell).

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

data [a] = [] | a : [a]

очень похоже на определение свободных монад

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

и Monadэкземпляр имеет сходство с Monoidэкземпляром для списков

--it needs to be a functor
instance Functor f => Functor (Free f) where
  fmap f (Pure a) = Pure (f a)
  fmap f (Roll x) = Roll (fmap (fmap f) x)

--this is the same thing as (++) basically
concatFree :: Functor f => Free f (Free f a) -> Free f a
concatFree (Pure x) = x
concatFree (Roll y) = Roll (fmap concatFree y)

instance Functor f => Monad (Free f) where
  return = Pure -- just like []
  x >>= f = concatFree (fmap f x)  --this is the standard concatMap definition of bind

теперь мы получаем две наши операции

-- this is essentially the same as \x -> [x]
liftFree :: Functor f => f a -> Free f a
liftFree x = Roll (fmap Pure x)

-- this is essentially the same as folding a list
foldFree :: Functor f => (f r -> r) -> Free f r -> r
foldFree _ (Pure a) = a
foldFree f (Roll x) = f (fmap (foldFree f) x)
Филипп JF
источник
12
Это может быть лучшим доступным объяснением «бесплатного», которое я когда-либо видел. Особенно абзац, начинающийся с «В целом».
Джон Л
16
Я думаю, что интересно посмотреть на то, Free f a = Pure a | Roll (f (Free f a))как Free f a = a + fa + ffa + ..., например, «F применяется к любое количество раз». Затем concatFree(то есть join) принимает «f применено любое количество раз к (f применено любое количество раз к a)» и объединяет два вложенных приложения в одно. И >>=принимает «f применено любое количество раз к a» и «как получить от a к (b с f применено любое количество раз)», и в основном применяет последнее к a внутри первого и разрушает вложение. Теперь я сама это понимаю!
2011 года
1
в concatFreeпринципе join?
Ргринберг
11
«Вот, пожалуй, более доступное объяснение. […] Фактически, поскольку монады можно рассматривать как моноиды в категории эндофункторов… »Тем не менее, я думаю, что это очень хороший ответ.
Рууд
2
«Монады можно рассматривать как моноиды в категории эндо-функторов» <3 (вы должны сослаться на stackoverflow.com/a/3870310/1306877, потому что каждый хакеллер должен знать об этой ссылке!)
до
419

Вот еще более простой ответ: Монада - это то, что «вычисляется», когда монадический контекст разрушается join :: m (m a) -> m a(напоминая, что >>=это можно определить как x >>= y = join (fmap y x)). Вот как монады переносят контекст через последовательную цепочку вычислений: потому что в каждой точке ряда контекст предыдущего вызова свернут со следующим.

А свободная монада удовлетворяет все законы Монады, но не делают какое - либо рушится (то есть вычисление). Он просто создает вложенную серию контекстов. Пользователь, который создает такое бесплатное монадическое значение, отвечает за то, чтобы что-то делать с этими вложенными контекстами, так что значение такой композиции может быть отложено до тех пор, пока монадное значение не будет создано.

Джон Вигли
источник
8
Твои абзацы - отличное дополнение к посту Филиппа.
Дэвид
20
Мне очень нравится этот ответ.
Данидиаз
5
Может ли свободная монада заменить класс типа Монада? То есть, могу ли я написать программу, использующую только возврат и привязку свободной монады, а затем объединить результаты, используя любой Mwybe, List или любой другой тип, или даже сгенерировать несколько монадических представлений одной последовательности вызовов функций привязки / объединения. Не обращая внимания на дно и нетерминацию, то есть.
мистерби
2
Этот ответ помог, но я думаю, что это смутило бы меня, если бы я не встретил «присоединиться» на курсе NICTA и прочитал haskellforall.com/2012/06/… . Так что для меня, трюк , чтобы понять, чтобы прочитать много ответов , пока он не тонет в (NICTA Ссылка:. Github.com/NICTA/course/blob/master/src/Course/Bind.hs )
Мартин Capodici
1
этот ответ самый лучший
Curycu
143

Случайно, что бесплатный foo - это самая простая вещь, которая удовлетворяет всем законам «foo». То есть он полностью соответствует законам, необходимым для того, чтобы быть обманщиком, и ничего лишнего.

Забывчивый функтор - это тот, кто «забывает» часть структуры при переходе из одной категории в другую.

Данные функторы F : D -> C, и G : C -> D, мы говорим F -| G, Fприсоединены слева Gили Gприсоединены справа к Fвсякий раз, когда все a, b: F a -> bизоморфно a -> G b, где стрелки приходят из соответствующих категорий.

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

Свободный моноид

Давайте начнем с более простого примера - свободного моноида.

Возьмите моноид, который определяется некоторым набором носителей T, бинарная функция помять пару элементов вместе f :: T → T → T, и unit :: T, таким образом, что у вас есть ассоциативный закон, и закон идентичности: f(unit,x) = x = f(x,unit).

Вы можете сделать функтор Uиз категории моноидов (где стрелки являются моноидными гомоморфизмами, то есть они гарантируют, что они сопоставляются unitс unitдругим моноидом, и что вы можете создавать до или после сопоставления с другим моноидом без изменения значения) в категорию наборов (где стрелки - просто функциональные стрелки), которые «забывают» об операции и unit, и просто дают вам набор несущих.

Затем вы можете определить функтор Fиз категории множеств обратно в категорию моноидов, оставленных присоединенными к этому функтору. Этот функтор является функтором, который отображает набор aв моноид [a], где unit = []и mappend = (++).

Итак, чтобы рассмотреть наш пример до сих пор, в псевдо-Haskell:

U : Mon  Set -- is our forgetful functor
U (a,mappend,mempty) = a

F : Set  Mon -- is our free functor
F a = ([a],(++),[])

Затем, чтобы показать, что Fэто бесплатно, мы должны продемонстрировать, что он оставлен присоединенным к Uзабывчивому функтору, то есть, как мы упоминали выше, нам нужно показать, что

F a → b изоморфен a → U b

помните, что цель Fнаходится в категории Monмоноидов, где стрелки являются моноидными гомоморфизмами, поэтому нам нужно показать, что гомоидизм моноидов из [a] → bможет быть точно описан функцией из a → b.

В Haskell мы называем ту сторону этого, которая живет в Set(то есть, Haskкатегория типов Haskell, которую мы притворяемся, это Set), просто foldMap, которая, когда специализируется от Data.FoldableLists, имеет тип Monoid m => (a → m) → [a] → m.

Из этого следствия вытекают последствия. Примечательно, что если вы забудете, то создайте бесплатно, а затем снова забудете, это так же, как вы однажды забыли, и мы можем использовать это для создания монадического соединения. так как UFUF~ U(FUF)~ UF, и мы можем перейти в тождестве моноидного гомоморфизма [a]с [a]помощью изоморфизма , который определяет наше примыкание, получаю , что список изоморфизмомом [a] → [a]является функцией типа a -> [a], и это просто вернуться к спискам.

Вы можете составить все это более непосредственно, описав список в следующих терминах:

newtype List a = List (forall b. Monoid b => (a -> b) -> b)

Свободная Монада

Так что же такое свободная монада ?

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

Итак, как это относится к понятию свободной монады, как это обычно используется?

Знание того, что что-то является свободной монадой, Free fговорит вам, что дать гомоморфизм монады из Free f -> m, это то же самое (изоморфно), что и дать естественное преобразование (гомоморфизм функторов) из f -> m. Помните, что он F a -> bдолжен быть изоморфным для того, a -> U bчтобы F оставался присоединенным к U. Здесь монады отображаются на функторы.

F, по крайней мере, изоморфен Freeтипу, который я использую в моем freeпакете при взломе.

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

class Algebra f x where
  phi :: f x -> x

newtype Free f a = Free (forall x. Algebra f x => (a -> x) -> x)

Кофейные Комонады

Мы можем построить нечто подобное, взглянув на правое сопряжение с забывчивым функтором, предполагая, что оно существует. Свободный функтор просто / прямо присоединен / к забывчивому функтору, и симметрично знать, что что-то является свободной комонадой, - это то же самое, что знать, что давать гомоморфизм комонады из w -> Cofree f- это то же самое, что давать естественное преобразование из w -> f.

Эдвард КМЕТТ
источник
12
@PauloScardine, это не то, о чем вы должны беспокоиться. Мой вопрос возник из интереса понять некоторую продвинутую структуру данных и, возможно, получить представление о том, что является самым передовым в разработке на Haskell прямо сейчас - это ни в коем случае не является необходимым или репрезентативным для того, что на самом деле написано на Haskell. (И еще одно: лучше, когда вы снова пройдете этап обучения IO)
Дэвид
8
@PauloScardine Вам не нужен вышеуказанный ответ для продуктивного программирования на Haskell, даже с бесплатными монадами. На самом деле, я бы не рекомендовал атаковать свободную монаду таким образом, чтобы тот, кто не имел опыта в теории категорий. Есть много способов поговорить об этом с оперативной точки зрения и понять, как их использовать, не вдаваясь в теорию категорий. Однако я не могу ответить на вопрос о том, откуда они берутся, не углубляясь в теорию. Свободные конструкции - мощный инструмент в теории категорий, но вам не нужен этот фон для их использования.
Эдвард КМЕТТ
18
@PauloScardine: вам не нужно никаких исчислений, чтобы эффективно использовать Haskell и даже понимать, что вы делаете. Немного странно жаловаться на то, что «этот язык математичен», когда математика - это просто дополнительное благо, которое вы можете использовать для развлечения и получения прибыли. Вы не получаете эти вещи в большинстве императивных языков. Почему вы жалуетесь на дополнительные услуги? Вы можете просто выбрать НЕ рассуждать математически и подходить к нему так же, как к любому другому новому языку.
Сара
3
@Sarah: Мне еще предстоит увидеть документацию или разговор IRC о haskell, который не сильно влияет на теорию компьютеров и термины лямбда-исчисления.
Пауло Скардин
11
@PauloScardine - это немного отвлекает ОТ, но в защиту Хаскелла: аналогичные технические вещи применимы ко всем остальным языкам программирования, только у Хаскелла такая хорошая компиляция, что людям действительно нравится говорить о них. Почему / как X является монадой интересной для многих, дискуссии о стандарте IEEE с плавающей запятой не интересны; оба случая не имеют значения для большинства людей, потому что они могут просто использовать результаты.
Дэвид
72

Свободная монада (структура данных) относится к монаде (классу), как список (структура данных) к моноиду (классу): это тривиальная реализация, в которой вы можете впоследствии решить, как будет комбинироваться контент.


Вы , наверное , знаете , что такое Монада и что каждая Монада нуждается в конкретных (Monad-законопослушные) осуществление любой fmap+ join+ returnили bind+ return.

Предположим, у вас есть Functor (реализация fmap), но все остальное зависит от значений и выборов, сделанных во время выполнения, что означает, что вы хотите иметь возможность использовать свойства Monad, но затем хотите выбрать функции Monad.

Это можно сделать с помощью Free Monad (структура данных), которая оборачивает Functor (тип) таким образом, что joinэто скорее сложение этих функторов, чем сокращение.

Реальное returnи которое joinвы хотите использовать, теперь могут быть заданы в качестве параметров функции сокращения foldFree:

foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a

Чтобы объяснить типы, мы можем заменить Functor fна Monad mи bна (m a):

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)
комонада
источник
8
Этот ответ дал мне впечатление, что я понимаю, для чего они могут быть полезны.
Дэвид
59

Свободная монада Haskell - это список функторов. Для сравнения:

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

data Free f r = Pure r | Free (f (Free f r))

Pureаналогично Nilи Freeаналогично Cons. Бесплатная монада хранит список функторов вместо списка значений. Технически, вы можете реализовать бесплатные монады, используя другой тип данных, но любая реализация должна быть изоморфна вышеупомянутой.

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

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

Габриэль Гонсалес
источник
6
Я знаю, что вы просто проводили аналогию, а не определяли, но свободная монада, безусловно, не является аналогом списка функторов в любом смысле. Это намного ближе к дереву функторов.
Том Эллис
6
Я придерживаюсь своей терминологии. Например, используя мой пакет index-core, вы можете определить «свободные монадные понимания», которые ведут себя так же, как монада списка, за исключением того, что вы привязываете функторы вместо значений. Свободная монада - это список функторов в том смысле, что если вы переводите все концепции Хаскелла в категорию функторов, то списки становятся свободными монадами. Истинное дерево функторов тогда становится чем-то совершенно другим.
Габриэль Гонсалес
4
Вы правы в том, что монада является классификацией в некотором смысле понятия моноида, поэтому свободные монады аналогичны свободным моноидам, то есть спискам. В этом смысле вы, безусловно, правы. Однако структура значения свободной монады не является списком. Это дерево, как я подробно опишу ниже .
Том Эллис
2
@TomEllis Технически, это только дерево, если ваш базовый функтор является функтором продукта. Когда в качестве базового функтора используется функтор суммы, он больше напоминает стековую машину.
Габриэль Гонсалес
21

Я думаю, что простой конкретный пример поможет. Предположим, у нас есть функтор

data F a = One a | Two a a | Two' a a | Three Int a a a

с очевидным fmap. Тогда Free F aтип деревьев, листья которых имеют тип aи чьи узлы помечены One, Two, Two'и Three. Oneузлы -nodes имеют одного дочернего элемента, Twoа Two'узлы -no имеют двух дочерних Threeузлов, а узлы -no имеют три дочерних элемента и также помечаются знаком Int.

Free Fэто монада returnотображается xна дерево, которое является просто листом со значением x. t >>= fсмотрит на каждое из листьев и заменяет их деревьями. Когда лист имеет значение, yон заменяет этот лист деревом f y.

Диаграмма делает это более понятным, но у меня нет возможности легко ее нарисовать!

Том Эллис
источник
14
Ребята, вы говорите, что свободная монада принимает форму самого функтора. Так что, если функтор древовидный (продукты), свободная монада древовидна; если она похожа на список (суммы), свободная монада похожа на список; если это похоже на функцию, свободная монада похожа на функцию; и т.д. Это имеет смысл для меня. Поэтому, как и в свободном моноиде, вы продолжаете рассматривать каждое приложение mappend как создание совершенно нового элемента; в свободной монаде вы рассматриваете каждое приложение функтора как совершенно новый элемент.
Бартош Милевски
4
Даже если функтор является «функтором суммы», получающаяся свободная монада все еще имеет древовидную форму. В результате у вас в дереве будет несколько типов узлов: по одному для каждого компонента вашей суммы. Если ваш «функтор суммы» X -> 1 + X, тогда вы действительно получите список, который является просто вырожденным видом дерева.
Том Эллис