Хорошие примеры Not a Functor / Functor / Applicative / Monad?

Объясняя кому-то, что такое класс типа X, я изо всех сил пытаюсь найти хорошие примеры структур данных, которые в точности соответствуют X.

Итак, прошу примеры:

  • Конструктор типа, не являющийся функтором.
  • Конструктор типа, который является Functor, но не Applicative.
  • Конструктор типа, который является аппликативом, но не монадой.
  • Конструктор типа, который является монадой.

Я думаю, что повсюду можно найти множество примеров Monad, но хороший пример Monad в некоторой связи с предыдущими примерами может дополнить картину.

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

Если бы удалось найти пример Arrow где-нибудь в этой иерархии (между Applicative и Monad?), Это тоже было бы здорово!


person Rotsor    schedule 28.08.2011    source источник
comment
Можно ли создать конструктор типа (* -> *), для которого не существует нет подходящего fmap?   -  person Owen    schedule 28.08.2011
comment
Оуэн, я думаю, a -> String не функтор.   -  person Rotsor    schedule 28.08.2011
comment
@Rotsor @Owen a -> String - математический функтор, но не Haskell Functor, для ясности.   -  person J. Abrahamson    schedule 04.09.2014
comment
@J. Абрахамсон, тогда в каком смысле это математический функтор? Вы про категорию с перевернутыми стрелками?   -  person Rotsor    schedule 05.09.2014
comment
@Rotsor ага, именно контравариантный функтор.   -  person J. Abrahamson    schedule 05.09.2014
comment
Для людей, которые не знают, у контравариантного функтора есть fmap типа (a -> b) -> f b -> f a   -  person AJF    schedule 23.03.2015
comment
См. Typeclassopedia (и улучшите ее, добавив примеры отсюда, где это уместно).   -  person ShreevatsaR    schedule 21.04.2018


Ответы (5)


Конструктор типа, не являющийся функтором:

newtype T a = T (a -> Int)

Из него можно сделать контравариантный функтор, но не (ковариантный) функтор. Попробуйте написать fmap, и у вас ничего не получится. Обратите внимание, что версия контравариантного функтора перевернута:

fmap      :: Functor f       => (a -> b) -> f a -> f b
contramap :: Contravariant f => (a -> b) -> f b -> f a

Конструктор типа, который является функтором, но не аппликативным:

У меня нет хорошего примера. Есть Const, но в идеале мне нужен конкретный немоноид, и я ничего не могу придумать. Все типы в основном числовые, перечисления, продукты, суммы или функции, когда вы дошли до этого. Ниже вы можете увидеть, что мы с свинарником не согласны с тем, является ли Data.Void Monoid;

instance Monoid Data.Void where
    mempty = undefined
    mappend _ _ = undefined
    mconcat _ = undefined

Поскольку _|_ является допустимым значением в Haskell и фактически единственным допустимым значением Data.Void, это соответствует правилам Monoid. Я не уверен, при чем здесь unsafeCoerce, потому что ваша программа больше не гарантирует, что она не нарушит семантику Haskell, как только вы воспользуетесь какой-либо unsafe функцией.

В Haskell Wiki можно найти статью о нижних (ссылка) или небезопасных функциях (ссылка).

Интересно, можно ли создать такой конструктор типов, используя более богатую систему типов, такую ​​как Agda или Haskell с различными расширениями.

Конструктор типа, который является аппликативом, но не монадой:

newtype T a = T {multidimensional array of a}

Вы можете сделать из него аппликатив, например:

mkarray [(+10), (+100), id] <*> mkarray [1, 2]
  == mkarray [[11, 101, 1], [12, 102, 2]]

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

Конструктор типа, который является монадой:

[]

О стрелках:

Спрашивать, где находится Стрелка в этой иерархии, все равно что спрашивать, что это за «красная» форма. Обратите внимание на несоответствие вида:

Functor :: * -> *
Applicative :: * -> *
Monad :: * -> *

но,

Arrow :: * -> * -> *
person Dietrich Epp    schedule 28.08.2011
comment
Хороший список! Я бы предложил использовать что-нибудь попроще, например Either a, в качестве примера для последнего случая, так как это легче понять. - person fuz; 28.08.2011
comment
Спасибо! Однако я думал, что <*> - это «основная» функция для Applicative. Было бы неплохо привести пример функтора без <*>! Или <*> без pure не имеет смысла? - person Rotsor; 28.08.2011
comment
@Rotsor: Хм, моя формулировка отключена. Проблема в том, что вы можете реализовать и то, и другое, но идентичность аппликативных функторов сломается. Исправлю формулировку. - person Dietrich Epp; 28.08.2011
comment
@Dietrich, хорошо, я вижу, как pure сломан (некуда брать значение для Int), но <*> мог бы работать, делая пересечение доменов карты. Разве это не приносит Map Int половину пути от Functor к Applicative в каком-то (каком?) Смысле? - person Rotsor; 28.08.2011
comment
Разве на сайте planet.haskell.org недавно не было опубликовано, что стрелки - это не что иное, как аппликативные функторы? - person fuz; 28.08.2011
comment
Я подозреваю, что Map Int имеет аппликативное поведение для заполнения. ‹*› Объединяет карты точечно, если любой из них определен, используя изображение с номером 0 по умолчанию, если оно существует. Сделайте pure определенным только в 0, и вы получите свое поведение fmap. - person pigworker; 28.08.2011
comment
@pigworker: Я удалил пример, я думал, что один из законов приложения нарушится, но, подумав, я понял, что ошибался, поскольку законы приложения не были симметричными, как я интуитивно предполагал. - person Dietrich Epp; 28.08.2011
comment
Можно изменить Map Int на forall b. Map b, чтобы сделать pure невозможным. - person Rotsor; 28.08.2011
comment
@Rotsor: С таким типом pure _ = undefined будет законной и правильной реализацией, поскольку в любом случае нет допустимых значений типа. - person Dietrich Epp; 28.08.2011
comment
@Rotsor: Упс, я имел в виду pure _ = T empty, потому что это единственное допустимое значение. - person Dietrich Epp; 28.08.2011
comment
Я имел в виду что-то вроде forall b. Ord b => Applicative (Map b), так что значения возможны, но мы не знаем, как их создать. - person Rotsor; 28.08.2011
comment
@Rotsor: В таком случае pure x может быть просто T $ fromList [(0::Int,x)], а <*> может применить крайний левый элемент левого операнда ко всем значениям в правом операнде, сохраняя ключи, чтобы вы могли создать законный экземпляр Applicative. - person Dietrich Epp; 28.08.2011
comment
@Dietrich, если вы можете реализовать Monoid для сумм и продуктов в общем виде, вы можете захотеть, чтобы тип функции имел некоторые трудности! :) - person Rotsor; 28.08.2011
comment
@Rotsor: К сожалению, вы можете создать моноид для любой функции, результатом которой является моноид. - person Dietrich Epp; 28.08.2011
comment
Строго говоря, числа с плавающей запятой не являются настоящими моноидами, потому что они не обладают ассоциативностью, не так ли? - person sclv; 29.08.2011
comment
@sclv: все, что имеет конечный набор значений, можно считать циклическим моноидом, независимо от того, имеет ли это смысл делать это или нет. - person Dietrich Epp; 30.08.2011
comment
И, конечно же, я только что понял, что все, что связано с полным порядком, может быть моноидом по минимуму / максимуму (и вы можете назначить что угодно достаточно произвольное частичное упорядочение), так что ... erp. - person sclv; 30.08.2011
comment
Если вы все еще ищете конструктор типа Applicative, но не Monad, очень распространенным примером будет ZipList. - person John L; 06.08.2012
comment
@JohnL: Мой новый любимый пример в этой конкретной категории - это параллелизируемые транзакции базы данных, которые вынуждены быть последовательными при преобразовании в монаду. - person Dietrich Epp; 06.08.2012
comment
_|_ обитает в каждом типе в *, но суть Void в том, что вам придется наклониться назад, чтобы построить один, иначе вы уничтожили его значение. Вот почему это не экземпляр Enum, Monoid и т. Д. Если он у вас уже есть, я рад позволить вам смешать их вместе (давая вам Semigroup), но mempty, но я не даю никаких инструментов для явного создания значения введите Void в void. Вы должны зарядить пистолет, направить его на ногу и самому нажать на спусковой крючок. - person Edward KMETT; 11.12.2012
comment
Педантично, я считаю, что ваше представление о Cofunctor неверно. Двойник функтора - это функтор, потому что вы переворачиваете оба входных и выходных данных и в итоге получаете одно и то же. Понятие, которое вы ищете, вероятно, является контравариантным функтором, который немного отличается. - person Ben Millwood; 16.05.2013
comment
@BenMillwood: Я использую контравариантный функтор в качестве определения для кофунктора, потому что контравариантный функтор - неудобно длинное имя для конструктора типа. Википедия поддерживает такое использование. - person Dietrich Epp; 16.05.2013
comment
@DietrichEpp: я считаю, что это использование неверно (цитирования нет), спросил math.SE, согласны ли они со мной. - person Ben Millwood; 17.05.2013
comment
Стрелка идеально подходит между Applicative и Monad. Каждую монаду можно превратить в стрелку (стрелки Клейсли), а каждая стрелка (с указанным первым параметром) является аппликативной (стрелка-оболочка). Несоответствие вида не имеет значения. - person Hjulle; 02.12.2013
comment
Привет, извините за старый вопрос, но есть ли какой-нибудь неочевидный пример базового встроенного типа Haskell (имеется в виду не какой-то эзотерический), похожий на монаду, но не являющийся таковой? - person Dan M.; 11.07.2016
comment
@DanM .: Я не уверен, что вы имеете в виду под словом "монада", и в Haskell не так уж много базовых встроенных типов ("встроенные" вы имеете в виду в Prelude?) - person Dietrich Epp; 11.07.2016
comment
@DietrichEpp Да, я имею в виду, что они стандартные и широко используются, а не ваши собственные. Я нашел ZipList, но я не очень знаком с ним (я использовал функции zip / zipWith, но не знал об этом. Также Set может быть хорошим примером. - person Dan M.; 11.07.2016
comment
@DanM .: Set не может быть монадой, потому что это даже не функтор, потому что он навязывает структуру своим членам. ZipList мне не кажется монадой. Одна из проблем здесь в том, что похоже на монаду - это очень субъективный вопрос. - person Dietrich Epp; 11.07.2016
comment
@DietrichEpp, однако, я видел несколько успешных (до некоторой степени) попыток сделать Set монадическим (например, с использованием монады Cont). Также есть этот пакет: hackage.haskell.org/package/set-monad Да, думаю, это довольно субъективно. Я просто искал типы, которые можно спутать с монадой, но на самом деле это не так. - person Dan M.; 11.07.2016
comment
@DietrichEpp привет, прошло 5 лет, и теперь устарел. Кроме того, в соответствии с этим вопросом МО функтором является самодвойственный, поэтому называть контравариантный функтор кофунктором будет ошибкой. Обновите ответ, потому что он так хорош для новичков, как я! - person Alex Vong; 17.09.2016
comment
@AlexVong: Устарело - ›люди просто используют другой пакет. Говоря о контравариантном функторе, не двойственном функтору, извините за путаницу. В некоторых контекстах я видел, что кофунктор используется для обозначения контравариантных функторов, потому что функторы самодвойственны, но, похоже, это просто сбивает людей с толку. - person Dietrich Epp; 17.09.2016
comment
Я считаю, что многомерный массив как пример аппликативного функтора-не-монады не совсем корректен. Почему именно невозможно определить экземпляр монады для этого конструктора типа? Ниже (в своем ответе на этот вопрос) я привожу конкретные примеры аппликативных функторов, которые являются либо монадами, либо не монадами. В частности, конструктор типа F a, который содержит произведение любого количества a, всегда является монадой. - person winitzki; 20.04.2018
comment
@winitzki: Причина, по которой это невозможно, - несоответствие размеров. Ранг многомерного массива не кодируется типом, он кодируется значением. Я не думаю, что то, что вы описываете, является эквивалентной структурой. - person Dietrich Epp; 20.04.2018
comment
@winitzki: Если это поможет, подумайте о <*> как о тензорном произведении, за исключением применения функций вместо умножения. - person Dietrich Epp; 20.04.2018
comment
@DietrichEpp Я не понимаю, что вы говорите. Можете ли вы указать на конкретный пример кода для многомерного массива? Также я не понимаю, как это помогает рассматривать <*> как тензорное произведение. Скорее соглашусь, что моноидальная операция mult: F a -> F b -> F (a, b) похожа на тензорное произведение. Но я до сих пор не понимаю, как это помогает проверить, что у тензорных массивов нет экземпляра монады. - person winitzki; 21.04.2018
comment
Дело не в том, что у тензоров нет экземпляра монады, а в том, что любой экземпляр монады не может быть расширением экземпляра аппликативного функтора, который я описал. - person Dietrich Epp; 21.04.2018
comment
Для любого типа e, который не указан, (e,-) является функтором, но не аппликативом. - person Student; 01.07.2020
comment
System.Console.GetOpt.ArgDescr, например, является функтором, но не аппликативным, и его нельзя сделать аппликативным (или моноидом), потому что он имеет несколько конструкторов, содержащих его аргумент типа. Более простой пример - data NotApplicative a = NALeft a | NARight a. Вам нужно было бы каким-то образом придумать способ составить информацию о том, NALeft или NARight, чтобы добраться куда-нибудь, что возможно, но я не думаю, что есть что-то, что вы могли бы прочитать только из определения типа. - person CodenameLambda; 20.07.2020

Мой стиль может быть ограничен моим телефоном, но начнем.

newtype Not x = Kill {kill :: x -> Void}

не может быть функтором. Если бы это было так, у нас было бы

kill (fmap (const ()) (Kill id)) () :: Void

а Луна будет сделана из зеленого сыра.

тем временем

newtype Dead x = Oops {oops :: Void}

является функтором

instance Functor Dead where
  fmap f (Oops corpse) = Oops corpse

но не может быть аппликативным, иначе у нас было бы

oops (pure ()) :: Void

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

(Дополнительное примечание: Void, поскольку в Data.Void - пустой тип данных. Если вы попытаетесь использовать undefined, чтобы доказать, что это моноид, я буду использовать unsafeCoerce, чтобы доказать, что это не так.)

Радостно,

newtype Boo x = Boo {boo :: Bool}

является аппликативным во многих отношениях, например, как сказал бы Дейкстра,

instance Applicative Boo where
  pure _ = Boo True
  Boo b1 <*> Boo b2 = Boo (b1 == b2)

но это не может быть Монада. Чтобы понять, почему нет, обратите внимание, что возврат должен быть постоянно Boo True или Boo False, и, следовательно,

join . return == id

невозможно удержать.

О да, чуть не забыл

newtype Thud x = The {only :: ()}

это монада. Сверните свой собственный.

Самолет ловить ...

person pigworker    schedule 28.08.2011
comment
У меня создалось впечатление, что Dead x может быть аппликативным, пока Void является Monoid, что, безусловно, имеет место, если это синглтон ... что такое Void, в точности? - person Dietrich Epp; 28.08.2011
comment
Пустота пуста! Во всяком случае, морально. - person pigworker; 28.08.2011
comment
Полагаю, Void - это тип с 0 конструкторами. Это не моноид, потому что нет mempty. - person Rotsor; 28.08.2011
comment
Если у него нет конструкторов, то pure _ = undefined и _ <*> _ = undefined являются законными и следуют применимым правилам. - person Dietrich Epp; 28.08.2011
comment
@Dietrich, undefined на самом деле не является значением, поэтому это рассуждение неверно. <*> действительно возможно, но для этого требуется сопоставление с образцом с 0 предложениями, что синтаксически запрещено в Haskell, но pure может быть вызвано, но не может возвращать значение. - person Rotsor; 28.08.2011
comment
неопределенный? Как грубо! К сожалению, unsafeCoerce (unsafeCoerce () ‹*› undefined) не (), поэтому в реальной жизни есть наблюдения, которые нарушают законы. - person pigworker; 28.08.2011
comment
Какие? Неопределенный (или _|_) на самом деле является значением ... например, это единственное значение типа Void. Поскольку Oops является строгим, все значения типа Dead x также являются _|_. - person Dietrich Epp; 28.08.2011
comment
@pigworker: unsafeCoerce содержит слово небезопасно, что указывает на то, что его МОЖНО использовать для нарушения семантики Haskell. С другой стороны, undefined - четко определенная часть семантики Haskell. - person Dietrich Epp; 28.08.2011
comment
@ Дитрих, это может быть верным аргументом. Это показывает, что тогда в Haskell нет пустых типов данных. - person Rotsor; 28.08.2011
comment
В обычной семантике, допускающей ровно один вид undefined, вы совершенно правы. Конечно, есть и другие смыслы. Пустота не ограничивается субмоноидом в общем фрагменте. Это также не моноид в семантике, которая различает виды отказа. Когда у меня будет момент, когда редактирование будет проще, чем на телефоне, я поясню, что мой пример работает только в семантике, для которой не существует точно одного вида undefined. - person pigworker; 28.08.2011
comment
@ Дитрих Эпп: Допустим, мы решили, как сказал Карри, не убегать от парадоксов. _|_ - это значение в некотором смысле, но, очевидно, мы не можем наблюдать его напрямую. Однако мы можем наблюдать аспекты _|_ другими способами, например, перехватывая исключения в IO или лично наблюдая незавершенность по сравнению с error. Итак, когда мы закрываем глаза и представляем, что это область Agda, которая вызывает Void пустой тип, можете ли вы показать, что предложенный вами Applicative экземпляр будет подчиняться законам, применимым к тому типу _|_, который он производит? - person C. A. McCann; 28.08.2011
comment
@C. А. Макканн: Перехват исключений в монаде ввода-вывода - это своего рода открытый секретный способ нарушения семантики Haskell; вот почему IO называется нечистым, даже если он монадически хорошо типизирован. По сути, вы просите функцию f :: _|_ -> _|_ сохранить точное нижнее значение, когда, согласно денотационной семантике Haskell, существует только одно такое значение. И если прикладные законы не были написаны для денотационной семантики Haskell, для чего были они написаны? - person Dietrich Epp; 28.08.2011
comment
@ Дитрих Эпп: Этот тип не имеет смысла. _|_ - это значение, а не тип, и на самом деле я не верю, что есть хоть что-нибудь, в котором говорится, что _|_ значения разных типов можно сравнивать, даже концептуально. Я сомневаюсь, что имеет смысл даже рассматривать равенство _|_ одного и того же типа, поэтому я не уверен, что выполнение законов классов типов - это вообще правильно сформулированный вопрос без введения чего-то, что позволяет как-то сравнивать значения _|_. - person C. A. McCann; 28.08.2011
comment
@C. А. Макканн: _|_ в сигнатуре типа было сокращением для типа, который содержит только это значение. (Денотационная семантика ясна: есть только один _|_.) Однако я думаю, что основная проблема здесь в том, что мы действительно не понимаем, как _|_ должен взаимодействовать с этими идентичностями в первую очередь, выбор Void просто вынуждает нас Смирись с этим. С другой стороны, если мы решим, что не можем рассматривать равенство _|_, тогда newtype T a = T Void также не может быть функтором, помимо того, что он не может быть аппликативным. - person Dietrich Epp; 28.08.2011
comment
@ Дитрих Эпп: Там тоже только один [], но спрашивать, равны ли пустые списки разных типов, остается нонсенсом; Кроме того, я не уверен, действительно ли семантическая уникальность оправдывает использование _|_ в ... эквивалентностях? Равенства? Личности? Какими бы ни были законы классов типов, если они даже четко определены (я почти уверен, что они не могут быть идентичностями). Экземпляр Functor гораздо менее сомнительный, потому что он избегает проблемы, просто сохраняя существующий _|_, а не вводя или удаляя _|_ значения. - person C. A. McCann; 28.08.2011
comment
@C. А. Макканн: Не понимаю, почему вы думаете, что я сравниваю значения разных типов. Как я уже сказал, настоящая проблема здесь в том, что мы так и не определили, что _|_ означает в Применимых законах. Но семантика Haskell связана с заменяемостью: я могу без жалоб заменить свой 12 на ваш 12, _|_ не должен подвергаться особому обращению. И есть что-то особенно странное в жалобах на неправильное значение типа Void. - person Dietrich Epp; 28.08.2011
comment
@pigworker Вы утверждаете, что unsafeCoerce (unsafeCoerce () <*> undefined) не () и, следовательно, это плохой Applicative пример, но ни один из Applicative законов, похоже, не имеет такой формы. (Согласно это list.) Почему вы думаете, что это дурацкое выражение должно оцениваться как ()? - person Daniel Wagner; 29.08.2011
comment
@Dietrich Epp IMO Применимые законы написаны для PER общих значений денотационной семантики Haskell. См. Быстрые и вольные рассуждения морально верны . - person Russell O'Connor; 05.09.2011
comment
вы можете сделать что угодно из значения в Void, например, доказательство ложного уравнения между типами; конечно, единственная проблема заключается в том, что ваши данные добываются до дна, поэтому, пока сбой или зацикливание с дном не считаются ошибочными, ничего не пойдет не так. - person pigworker; 03.03.2013
comment
Большинство глупых типов данных, используемых в этом ответе, изоморфны реальным. Например, Dead изоморфен V1 и Thud изоморфен Proxy. - person Joseph Sible-Reinstate Monica; 20.10.2019

Я считаю, что в других ответах пропущены несколько простых и распространенных примеров:

Конструктор типа, который является функтором, но не аппликативом. Простым примером является пара:

instance Functor ((,) r) where
    fmap f (x,y) = (x, f y)

Но невозможно определить его Applicative экземпляр без наложения дополнительных ограничений на r. В частности, нет способа определить pure :: a -> (r, a) для произвольного r.

Конструктор типа, который является аппликативом, но не монадой. Хорошо известный пример - ZipList. (Это newtype, который обертывает списки и предоставляет для них разные Applicative экземпляры.)

fmap определяется обычным образом. Но pure и <*> определены как

pure x                    = ZipList (repeat x)
ZipList fs <*> ZipList xs = ZipList (zipWith id fs xs)

поэтому pure создает бесконечный список, повторяя заданное значение, а <*> заархивирует список функций со списком значений - применяет i -ю функцию к i -ому элементу. (Стандарт <*> на [] производит все возможные комбинации применения i -ой функции к j -ому элементу.) Но нет разумного способа определить монаду (см. этот пост).


Как стрелки вписываются в иерархию функторов / аппликативов / монад? См. Идиомы не обращают внимания, стрелки точны, монады беспорядочны Сэм Линдли, Филип Уодлер, Джереми Яллоп. MSFP 2008. (Они называют аппликативными функторами идиомы.) Аннотация:

Мы возвращаемся к связи между тремя понятиями вычислений: монадами Моджи, стрелами Хьюза и идиомами Макбрайда и Патерсона (также называемыми аппликативными функторами). Мы показываем, что идиомы эквивалентны стрелкам, удовлетворяющим изоморфизму типов A ~> B = 1 ~> (A -> B), и что монады эквивалентны стрелкам, удовлетворяющим изоморфизму типов A ~> B = A -> (1 ~ > Б). Кроме того, идиомы встраиваются в стрелки, а стрелки встраиваются в монады.

person Petr    schedule 27.08.2012
comment
Итак, ((,) r) - это функтор, который не является аппликативом; но это только потому, что вы не можете определить pure для всех r сразу. Следовательно, это причуда краткости языка, попытка определить (бесконечный) набор аппликативных функторов с одним определением pure и <*>; в этом смысле в этом контрпримере нет ничего математически глубокого, поскольку для любого конкретного r, ((,) r) можно сделать аппликативным функтором. Вопрос: Вы можете придумать функтор CONCRETE, который не может быть аппликативом? - person George; 23.05.2017
comment
См. stackoverflow.com/questions/44125484/ как сообщение с этим вопросом. - person George; 23.05.2017

Хорошим примером конструктора типа, который не является функтором, является Set: вы не можете реализовать fmap :: (a -> b) -> f a -> f b, потому что без дополнительного ограничения Ord b вы не можете построить f b.

person Landei    schedule 29.08.2011
comment
На самом деле это хороший пример, поскольку с математической точки зрения мы действительно хотели бы сделать его функтором. - person Alexandre C.; 25.10.2011
comment
@AlexandreC. Я бы не согласился с этим, это не очень хороший пример. Математически такая структура данных действительно образует функтор. Тот факт, что мы не можем реализовать fmap, является проблемой языка / реализации. Кроме того, можно обернуть Set в монаду продолжения, которая делает из нее монаду со всеми ожидаемыми свойствами, см. это вопрос (хотя я не уверен, что это можно сделать эффективно). - person Petr; 29.08.2012
comment
@PetrPudlak, как это языковой вопрос? Равенство b может быть неразрешимым, в этом случае вы не можете определить fmap! - person Turion; 10.04.2018
comment
@Turion Разрешимость и определимость - две разные вещи. Например, можно правильно определить равенство лямбда-терминов (программ), даже если это невозможно решить с помощью алгоритма. В любом случае, в этом примере дело обстоит не так. Проблема здесь в том, что мы не можем определить экземпляр Functor с ограничением Ord, но это может быть возможно с другим определением Functor или лучшей языковой поддержкой. На самом деле с ConstraintKinds можно определить класс типа, который можно параметризовать следующим образом. - person Petr; 10.04.2018
comment
Даже если бы мы смогли преодолеть ограничение ord, тот факт, что Set не может содержать повторяющиеся записи, означает, что fmap может изменить контекст. Это нарушает закон ассоциативности. - person John F. Miller; 08.03.2019

Я хотел бы предложить более систематический подход к ответу на этот вопрос, а также показать примеры, в которых не используются какие-либо специальные приемы, такие как «нижние» значения или бесконечные типы данных или что-то в этом роде.

Когда конструкторы типов не могут иметь экземпляры классов типов?

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

  1. Невозможно реализовать сигнатуры типов требуемых методов из класса типа.
  2. Может реализовывать типовые подписи, но не может соответствовать требуемым законам.

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

Конкретные примеры

  • Конструктор типа, у которого не может быть экземпляра функтора, потому что тип не может быть реализован:

    data F z a = F (a -> z)
    

Это контрафунктор, а не функтор, по отношению к параметру типа a, потому что a находится в контравариантном положении. Невозможно реализовать функцию с сигнатурой типа (a -> b) -> F z a -> F z b.

  • Конструктор типа, который не является законным функтором, хотя сигнатура типа fmap может быть реализована:

    data Q a = Q(a -> Int, a)
    fmap :: (a -> b) -> Q a -> Q b
    fmap f (Q(g, x)) = Q(\_ -> g x, f x)  -- this fails the functor laws!
    

Любопытный аспект этого примера заключается в том, что мы можем реализовать fmap правильного типа, даже если F не может быть функтором, потому что он использует a в контравариантной позиции. Таким образом, эта реализация fmap, показанная выше, вводит в заблуждение - даже несмотря на то, что она имеет правильную сигнатуру типа (я считаю, что это единственная возможная реализация этой сигнатуры типа), законы функторов не выполняются. Например, fmap idid, потому что let (Q(f,_)) = fmap id (Q(read,"123")) in f "456" это 123, а let (Q(f,_)) = id (Q(read,"123")) in f "456" это 456.

Фактически F - это только профунктор, он не является ни функтором, ни контрафунктором.

  • Законный функтор, который не является аппликативным, поскольку сигнатура типа pure не может быть реализована: возьмите монаду Writer (a, w) и удалите ограничение, согласно которому w должен быть моноидом. Тогда невозможно построить значение типа (a, w) из a.

  • Функтор, который не является аппликативным, поскольку сигнатура типа <*> не может быть реализована: data F a = Either (Int -> a) (String -> a).

  • Функтор, не имеющий законного применения, хотя методы класса типа могут быть реализованы:

    data P a = P ((a -> Int) -> Maybe a)
    

Конструктор типа P является функтором, потому что он использует a только в ковариантных позициях.

instance Functor P where
   fmap :: (a -> b) -> P a -> P b
   fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))

Единственно возможная реализация сигнатуры типа <*> - это функция, которая всегда возвращает Nothing:

 (<*>) :: P (a -> b) -> P a -> P b
 (P pfab) <*> (P pa) = \_ -> Nothing  -- fails the laws!

Но эта реализация не удовлетворяет закону тождества для аппликативных функторов.

  • Функтор Applicative, но не Monad, потому что подпись типа bind не может быть реализована.

Я таких примеров не знаю!

  • Функтор Applicative, но не Monad, потому что законы не могут быть выполнены, даже если подпись типа bind может быть реализована.

Этот пример вызвал немало дискуссий, поэтому можно с уверенностью сказать, что доказать его правильность непросто. Но несколько человек проверили это независимо разными методами. См. Является ли `data PoE a = Empty | Соедините монаду a` a? для дополнительного обсуждения.

 data B a = Maybe (a, a)
   deriving Functor

 instance Applicative B where
   pure x = Just (x, x)
   b1 <*> b2 = case (b1, b2) of
     (Just (x1, y1), Just (x2, y2)) -> Just((x1, x2), (y1, y2))
     _ -> Nothing

Довольно громоздко доказывать отсутствие законного Monad случая. Причина немонадического поведения в том, что нет естественного способа реализации bind, когда функция f :: a -> B b могла бы возвращать Nothing или Just для разных значений a.

Возможно, будет более понятным рассмотреть Maybe (a, a, a), который также не является монадой, и попробовать реализовать join для этого. Вы обнаружите, что не существует интуитивно разумного способа реализации join.

 join :: Maybe (Maybe (a, a, a), Maybe (a, a, a), Maybe (a, a, a)) -> Maybe (a, a, a)
 join Nothing = Nothing
 join Just (Nothing, Just (x1,x2,x3), Just (y1,y2,y3)) = ???
 join Just (Just (x1,x2,x3), Nothing, Just (y1,y2,y3)) = ???
 -- etc.

В случаях, обозначенных ???, кажется очевидным, что мы не можем произвести Just (z1, z2, z3) каким-либо разумным и симметричным образом из шести различных значений типа a. Мы, конечно, могли бы выбрать какое-то произвольное подмножество из этих шести значений - например, всегда брать первое непустое Maybe - но это не удовлетворяло бы законам монады. Возврат Nothing также не будет соответствовать законам.

  • Древовидная структура данных, которая не является монадой, хотя она имеет ассоциативность для bind, но не соответствует законам идентичности.

Обычная древовидная монада (или «дерево с функторными ветвями») определяется как

 data Tr f a = Leaf a | Branch (f (Tr f a))

Это свободная монада над функтором f. Форма данных - дерево, где каждая точка ветвления представляет собой «функтор» поддеревьев. Стандартное двоичное дерево будет получено с помощью type f a = (a, a).

Если мы изменим эту структуру данных, сделав также листья в форме функтора f, мы получим то, что я называю «полумонада» - она ​​имеет bind, который удовлетворяет законам естественности и ассоциативности, но его pure метод не соответствует одному из тождеств. законы. «Полумонады - это полугруппы в категории эндофункторов, в чем проблема?» Это тип класса Bind.

Для простоты я определяю метод join вместо bind:

 data Trs f a = Leaf (f a) | Branch (f (Trs f a))
 join :: Trs f (Trs f a) -> Trs f a
 join (Leaf ftrs) = Branch ftrs
 join (Branch ftrstrs) = Branch (fmap @f join ftrstrs)

Прививка ветвей стандартная, а прививка листьев нестандартная и дает Branch. Это не проблема для закона ассоциативности, но нарушает один из законов тождества.

Когда у полиномиальных типов есть экземпляры монад?

Ни один из функторов Maybe (a, a) и Maybe (a, a, a) не может иметь законного Monad экземпляра, хотя они, очевидно, Applicative.

У этих функторов нет никаких уловок - никаких Void или bottom нигде, никакой хитрой лени / строгости, никаких бесконечных структур и никаких ограничений класса типов. Экземпляр Applicative полностью стандартный. Функции return и bind могут быть реализованы для этих функторов, но не будут удовлетворять законам монады. Другими словами, эти функторы не являются монадами, потому что отсутствует конкретная структура (но непросто понять, что именно отсутствует). Например, небольшое изменение функтора может превратить его в монаду: data Maybe a = Nothing | Just a - это монада. Другой подобный функтор data P12 a = Either a (a, a) также является монадой.

Конструкции для полиномиальных монад

В общем, вот некоторые конструкции, которые производят правильные Monad из полиномиальных типов. Во всех этих конструкциях M - монада:

  1. type M a = Either c (w, a) где w - любой моноид
  2. type M a = m (Either c (w, a)) где m - любая монада, а w - любой моноид
  3. type M a = (m1 a, m2 a) где m1 и m2 - любые монады
  4. type M a = Either a (m a) где m - любая монада

Первая конструкция WriterT w (Either c), вторая конструкция WriterT w (EitherT c m). Третья конструкция представляет собой покомпонентный продукт монад: pure @M определяется как покомпонентное произведение pure @m1 и pure @m2, а join @M определяется путем исключения данных по нескольким продуктам (например, m1 (m1 a, m2 a) отображается на m1 (m1 a), опуская вторую часть кортеж):

 join :: (m1 (m1 a, m2 a), m2 (m1 a, m2 a)) -> (m1 a, m2 a)
 join (m1x, m2x) = (join @m1 (fmap fst m1x), join @m2 (fmap snd m2x))

Четвертая конструкция определяется как

 data M m a = Either a (m a)
 instance Monad m => Monad M m where
    pure x = Left x
    join :: Either (M m a) (m (M m a)) -> M m a
    join (Left mma) = mma
    join (Right me) = Right $ join @m $ fmap @m squash me where
      squash :: M m a -> m a
      squash (Left x) = pure @m x
      squash (Right ma) = ma

Я проверил, что все четыре конструкции производят законные монады.

Я предполагаю, что других конструкций для полиномиальных монад не существует. Например, функтор Maybe (Either (a, a) (a, a, a, a)) не получается ни одной из этих конструкций и поэтому не является монадическим. Однако Either (a, a) (a, a, a) монадичен, потому что он изоморфен произведению трех монад a, a и Maybe a. Кроме того, Either (a,a) (a,a,a,a) является монадическим, потому что он изоморфен произведению a и Either a (a, a, a).

Четыре показанные выше конструкции позволят нам получить любую сумму любого количества произведений любого количества a, например Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a)) и так далее. У всех таких конструкторов типов будет (как минимум один) Monad экземпляр.

Конечно, еще неизвестно, какие варианты использования могут существовать для таких монад. Другая проблема заключается в том, что экземпляры Monad, полученные с помощью конструкций 1–4, в общем случае не уникальны. Например, конструктору типа type F a = Either a (a, a) можно дать экземпляр Monad двумя способами: конструкцией 4 с использованием монады (a, a) и конструкцией 3 с использованием изоморфизма типов Either a (a, a) = (a, Maybe a). Опять же, поиск вариантов использования этих реализаций не сразу очевиден.

Остается вопрос - учитывая произвольный полиномиальный тип данных, как распознать, есть ли у него экземпляр Monad. Я не знаю, как доказать, что других конструкций для полиномиальных монад не существует. Я не думаю, что пока существует какая-либо теория, чтобы ответить на этот вопрос.

person winitzki    schedule 07.04.2018
comment
Я думаю, B монада. Вы можете дать контрпример этой привязке Pair x y >>= f = case (f x, f y) of (Pair x' _,Pair _ y') -> Pair x' y' ; _ -> Empty? - person Franky; 08.04.2018
comment
@Franky Associativity терпит неудачу с этим определением, когда вы выбираете f так, чтобы f x было Empty, но f y было Pair, а на следующем шаге оба Pair. Я проверил вручную, что законы не действуют для этой реализации или для любой другой реализации. Но для этого нужно потрудиться. Я бы хотел, чтобы был более простой способ разобраться в этом! - person winitzki; 08.04.2018
comment
@winitzki Как не удается ассоциативность? Разве в любом случае вы не получите Empty? - person melpomene; 10.04.2018
comment
B b1 <*> B b2 = ... не имеет смысла, поскольку B не является конструктором значения. Кроме того, я думаю, что Pair (x1, x2) (y1, y2) имеет неправильный тип, должно быть Pair (x1 x2) (y1 y2)? - person chi; 10.04.2018
comment
Да, вы правы, @chi - мой синтаксис Haskell заржавел. - person winitzki; 10.04.2018
comment
Просто чтобы уточнить: в Either a (m a) вы намеревались иметь a дважды? Если так, то это может иметь какое-то объяснение - я не думаю, что формирователь монад - один из тех, что хорошо известны людям на Haskell. Если нет, то первый случай type L a = Either c (w, a) полностью подпадает под второй случай, поскольку (w, a) является монадой записи. - person Daniel Wagner; 10.04.2018
comment
@DanielWagner Да, это специально предназначено для того, чтобы a было дважды. Это нетривиальная конструкция. - person winitzki; 10.04.2018
comment
Как на самом деле доказать, что Monad экземпляра не существует? Ваш аргумент не является естественным способом реализации bind, когда функция f :: a -> B b могла бы возвращать Empty или Pair для разных значений a, также работает для Maybe. - person Turion; 10.04.2018
comment
@Turion Этот аргумент неприменим к Maybe, потому что Maybe не содержит других значений a, о которых нужно беспокоиться. - person Daniel Wagner; 10.04.2018
comment
@Turion Я доказал это парой страниц расчетов; аргумент о естественном пути - всего лишь эвристическое объяснение. Экземпляр Monad состоит из функций return и bind, которые удовлетворяют законам. Есть две реализации return и 25 реализаций bind, которые подходят для требуемых типов. Прямым расчетом можно показать, что ни одна из реализаций не удовлетворяет законам. Чтобы сократить объем необходимой работы, я использовал join вместо bind и сначала применил законы об идентичности. Но это была изрядная работа. - person winitzki; 10.04.2018
comment
@melpomene Я оглянулся на свои расчеты, и вы правы, - в данном случае не работает не ассоциативность, а один из законов тождества. - person winitzki; 10.04.2018
comment
Принятый ответ на этот вопрос: stackoverflow.com/questions/13034229/, утверждает, что Maybe (a, a) не является монадой, и дает частичное доказательство. У меня есть полное доказательство, но оно длинное. - person winitzki; 10.04.2018
comment
Из комментария к этому вопросу: Итак, вкратце: монадам разрешено отбрасывать информацию, и это нормально, если они просто вложены в себя. Но когда у вас есть монада, сохраняющая информацию, и монада, отбрасывающая информацию, при объединении двух данных отбрасывается информация, даже если сохраняющей информацию требуется, чтобы эта информация сохранялась, чтобы удовлетворять его собственным законам монад. Таким образом, вы не можете комбинировать произвольные монады. (Вот почему вам нужны Traversable монады, которые гарантируют, что они не будут отбрасывать релевантную информацию; они могут быть составлены произвольно.) Исправление: компоновка работает только одним способом: (Maybe a, Maybe a) - это монада. - person winitzki; 10.04.2018
comment
Здесь можно найти вопрос со счетчиками, в которых монада не работает: stackoverflow.com/questions/49742377/ - person Franky; 10.04.2018
comment
Очень интересный ответ. Небольшой вопрос в скобках: нужен ли вашему Either a (m a) экземпляру Traversable m? Я полагаю, что так, как если бы вы писали join, кажется, вам нужно было бы заменить m (Either a (m a)) на Either a (m (m a)). (Это не считается возражением против вашего экземпляра, поскольку все полиномиальные функторы Traversable.) - person duplode; 11.04.2018
comment
@duplode Нет, я не думаю, что Traversable нужен. m (Either a (m a)) преобразуется с помощью pure @m в m (Either (m a) (m a)). Тогда тривиально Either (m a) (m a) -> m a, и мы можем использовать join @m. Это была реализация, для которой я проверил законы. - person winitzki; 11.04.2018
comment
@winitzki Ой, здорово! Это как-то ускользнуло от меня. (Мне все еще нужно проверить, является ли экземпляр, использующий Traversable, законным. Я думаю, что это не так - по крайней мере, не для всех m.) - person duplode; 12.04.2018
comment
Хотя вы уже поняли, что (см. этот ваш комментарий в другой вопрос), вы можете обновить этот ответ, чтобы учесть, что Either (a,a) (a,a,a,a) на самом деле является монадой - она ​​изоморфна (a, Either a (a,a,a)). - person duplode; 15.04.2018
comment
@duplode Да, спасибо - я обновил ответ. Фактически, экземпляр Monad может быть определен для любой суммы любого количества произведений любого количества a и, возможно, более чем одним способом. Практическая полезность любой из этих монад еще предстоит определить. Например, что можно использовать для чего-то вроде Either (a, a, a) (a, a, a, a, a)? - person winitzki; 17.04.2018
comment
@winitzki [...] возможно, более чем одним способом - Ага, это красивый беспорядок. Either a (a, a) содержит два описанных здесь экземпляра, которые соответствуют (предупреждение: неправильное обозначение) a + a^2 и a * (1 + a). Если вы обновите его до One a | Two a a | Three a a a, вы получите два экземпляра, разложив их как a + (a * (a + a^2)) или a + (a^2 * (1 + a)) - они немного отличаются, что я не могу объяснить словами. Уточнение того, как пустота (как в Maybe (a, a) / 1 + a^2) мешает, может помочь разобраться во всем этом. - person duplode; 20.04.2018
comment
@duplode Спасибо, я реорганизовал и отредактировал свой ответ с учетом вашего примера. - person winitzki; 03.05.2018
comment
@winitzki Этот развернутый ответ действительно хорош. Кстати, я должен пересмотреть свой собственный ответ о диагоналях в другом вопросе - после этого обсуждения он кажется немного грубым. - person duplode; 03.05.2018
comment
Утверждение о том, что fmap для data F a = F (a -> Int) не может быть записано правильное по типу fmap, является ложным: fmap _ _ = F (\_ -> 0). Единственная проблема в том, что это незаконно. - person Joseph Sible-Reinstate Monica; 10.11.2019
comment
@JosephSible Спасибо, поправил текст. Теперь пример должен быть правильным. (a - ›z вместо a -› Int) - person winitzki; 17.11.2019