Каковы плюсы и минусы добавления дополнительных функций/комбинаторов в определение класса?
Глядя на источник для Монады:
class Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
{-# INLINE (>>) #-}
m >> k = m >>= \_ -> k -- <-- !! right here !!
fail s = error s
Вы можете видеть, что >>
имеет реализацию по умолчанию. Мой вопрос в том, считается ли это хорошей или плохой практикой и почему, чтобы включить функцию/комбинатор в класс, вместо того, чтобы предоставлять его отдельно за пределами класса?
То есть, почему бы и нет:
class Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
return :: a -> m a
fail :: String -> m a
fail s = error s
и где-то еще:
(>>) :: forall a b. m a -> m b -> m b
{-# INLINE (>>) #-}
m >> k = m >>= \_ -> k
Ответы
Ответ 1
Насколько я знаю, есть две основные причины включения "дополнительных" функций:
-
Эффективность. Иногда существует неэффективная универсальная реализация, и автор класса ожидает, что реализация конкретных экземпляров будет значительно лучше. В таких случаях, включая функцию класса с реализацией по умолчанию, это означает, что экземпляры могут использовать оптимизированные версии, если они этого хотят, но не требуются. Для примера, посмотрите Foldable
. Это также относится к Monad
.
-
Выбор реализации. Часто существует несколько подмножеств функций класса, которые могут быть использованы; включая все потенциальные функции и использование реализаций по умолчанию в терминах друг друга, означает, что экземпляр может выбрать некоторые функции для реализации и получить остальное автоматически. Это также относится к Foldable
, но Eq
- более простой пример.
Ответ 2
Таким образом, пользовательский >>
может быть реализован для монадов, где это можно сделать более эффективно или естественно, чем через m >>= \_ -> k
, но реализация по умолчанию все еще существует.
Ответ 3
Еще один аргумент для включения методов в класс классов - это когда они должны удовлетворять определенным законам или когда они делают утверждение этих законов более ясным. Я бы сказал, что законы должны морально ассоциироваться с классом стилей ( "что я должен предоставить, чтобы объявить экземпляр этого класса?" ) Например, вы можете предпочесть формулировки законов монады в терминах return
, join
и fmap
, а не return
и >>=
; который побуждает вас поместить все четыре оператора в класс типа (и сделать Monad
подкласс Functor
!) и дать определения по умолчанию >>=
в терминах join
и наоборот.