Почему Клейсли не является экземпляром Monoid?

Если вы хотите добавить две функции типа (a -> m b), чтобы вы получили только одну функцию того же типа, добавляя оба результата, вы можете использовать Kleisli для этого:

instance (Monad m, Monoid b) => Monoid (Kleisli m a b) where
    mempty = Kleisli (\_ -> return mempty)
    mappend k1 k2 =
        Kleisli g
            where
                g x = do
                    r1 <- runKleisli k1 x
                    r2 <- runKleisli k2 x
                    return (r1 <> r2)

Однако в настоящее время нет такого экземпляра, определенного в Control.Arrow, Как часто в Хаскеле, я подозреваю, что есть веская причина, но не могу найти какую.

Заметка

Этот вопрос довольно похож на этот. Тем не менее, с Monoid я не вижу способа определить экземпляр, такой как:

instance (Monad m, Monoid b) => Monoid (a -> m b) where
    [...]

поскольку уже есть экземпляр:

instance Monoid b => Monoid (a -> b) where
    [...]

1 ответ

Решение

В сфере проектирования библиотек мы сталкиваемся здесь с выбором, и мы решили быть не совсем последовательными в нашей коллективной политике (или ее отсутствии).

Monoid случаи для Monad (или же Applicative) Конструкторы типов могут появляться различными способами. Точечный подъем всегда доступен, но мы не определяем

instance (Applicative f, Monoid x) => Monoid (f x) {- not really -} where
  mempty         = pure mempty
  mappend fa fb  = mappend <$> fa <*> fb

Обратите внимание, что instance Monoid (a -> b) именно такой точечный подъем, поэтому точечный подъем для (a -> m b) случается всякий раз, когда экземпляр моноида для m b делает точечный подъем для моноида на b,

Мы не делаем точечный подъем вообще, не только потому, что это помешает другим Monoid случаи, носителями которых являются применяемые типы, а также потому, что структура f часто считается более значительным, чем у x, Ключевым примером является свободный моноид, более известный как [x], который является Monoid от [] а также (++)вместо точечного поднятия. Моноидальная структура происходит из переноса списка, а не из переносимых элементов.

Мое эмпирическое правило действительно заключается в том, чтобы расставлять приоритеты моноидальной структуре, присущей конструктору типов, над точечным поднятием или моноидальной структурой конкретных экземпляров типа, таких как моноид композиции для a -> a, Эти могут и действительно получить newtype обертываний.

Аргументы вспыхивают по поводу того, Monoid (m x) должен совпадать с MonadPlus m всякий раз, когда оба существуют (и аналогично Alternative). Я чувствую, что единственный хороший MonadPlus Экземпляр является копией Monoid пример, но другие отличаются. Тем не менее, библиотека не согласована в этом вопросе, особенно не в том, что (многие читатели видели, как этот мой старый багбир придет)...

... моноидный экземпляр для Maybe, который игнорирует тот факт, что мы обычно используем Maybe смоделировать возможный сбой и вместо этого заметить, что та же самая идея типа данных о добавлении дополнительного элемента может быть использована для того, чтобы дать полугруппе нейтральный элемент, если он еще не был. Эти две конструкции порождают изоморфные типы, но они не являются концептуально родственными. (Править Чтобы сделать вещи хуже, идея реализована неловко, давая пример Monoid ограничение, когда только Semigroup нужно. Я хотел бы увидеть Semigroup-extends-toMonoid идея реализована, но не для Maybe.)

Возвращаясь к Kleisli в частности, у нас есть три очевидных варианта-кандидата:

  1. Monoid (Kleisli m a a) с return и Клейсли состав
  2. MonadPlus m => Monoid (Kleisli m a b) лифтинг mzero а также mplus точечно над ->
  3. Monoid b => Monoid (Kleisli m a b) поднимая моноидную структуру b над m затем ->

Я ожидаю, что выбор не был сделан, просто потому, что не ясно, какой выбор сделать. Я не решаюсь сказать это, но мой голос будет за 2, отдавая приоритет структуре, исходящей от Kleisli m a над структурой из b,

Другие вопросы по тегам