Хорошие примеры не функтор / функтор / аппликатив / монада? - PullRequest
196 голосов
/ 28 августа 2011

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

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

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

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

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

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

Ответы [ 5 ]

93 голосов
/ 28 августа 2011

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

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это соответствует правилам моноида.Я не уверен, что 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 :: * -> * -> *
82 голосов
/ 28 августа 2011

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

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 :: ()}

- это Монада. Бросай свой.

Самолет, чтобы поймать ...

67 голосов
/ 27 августа 2012

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

Конструктор типа, являющийся Функтором, но не Аппликативным. Простой пример - пара:

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 ~> Б).Далее, идиомы встраиваются в стрелки, а стрелки - в монады.

20 голосов
/ 29 августа 2011

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

7 голосов
/ 07 апреля 2018

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

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

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

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

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

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

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

    data F a = F (a -> Int)
    

Это контраунктор, а не функтор, поскольку он использует параметр типа a в контравариантномпозиция.Невозможно реализовать функцию с сигнатурой типа (a -> b) -> F a -> F 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, показанная выше, вводит в заблуждение - даже если она имеет правильную сигнатуру типа (я считаю, что это единственно возможная реализация этой сигнатуры типа), законы функтора не выполняются (для этого требуются некоторые простые вычисления).

На самом деле, 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 может быть реализована.

Этот пример вызвал немало дискуссий, поэтому можно с уверенностью сказать, что доказать правильность этого примера нелегко.Но несколько человек подтвердили это независимо разными способами.См. Является ли `PoE данных 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

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

I гипотеза о том, что нет других конструкций для полиномиальных монад. Например, функтор 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.Я не знаю, как доказать, что нет других конструкций для полиномиальных монад.Я не думаю, что до сих пор существует какая-либо теория для ответа на этот вопрос.

...