it-swarm.com.ru

Хорошие примеры не функтор / функтор / аппликатив / монада?

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

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

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

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

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

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

196
Rotsor

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

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.VoidMonoid;

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 :: * -> * -> *
93
Dietrich Epp

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

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, чтобы доказать, что это Monoid, я буду использовать unsafeCoerce, чтобы доказать, что это не так.)

Радостно,

newtype Boo x = Boo {boo :: Bool}

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

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

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

join . return == id

не может удержаться.

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

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

это монада Катайся сам.

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

82
pigworker

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

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

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 - й функции к j --ому элементу.) Но есть нет разумного способа, как определить монаду (см. этот пост ).


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

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

67
Petr Pudlák

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

20
Landei

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

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

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

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

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

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

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

    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.

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

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

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

7
winitzki