Почему Клейсли не является экземпляром 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
в частности, у нас есть три очевидных варианта-кандидата:
Monoid (Kleisli m a a)
сreturn
и Клейсли составMonadPlus m => Monoid (Kleisli m a b)
лифтингmzero
а такжеmplus
точечно над->
Monoid b => Monoid (Kleisli m a b)
поднимая моноидную структуруb
надm
затем->
Я ожидаю, что выбор не был сделан, просто потому, что не ясно, какой выбор сделать. Я не решаюсь сказать это, но мой голос будет за 2, отдавая приоритет структуре, исходящей от Kleisli m a
над структурой из b
,