Почему экземпляр Полугруппы Maybe смещен в сторону Just, а Monoid использует Nothing в качестве пустого элемента?
Maybe
выражает вычисления, которые могут не дать результата из-за ошибки. Поэтому такие вычисления должны быть короткими.
Сейчас Maybe
Похоже, что экземпляры полугруппы / моноида нарушают эту семантику, потому что первая склонна к Just
и последний относится к ошибке Nothing
как его пустой элемент:
Just "foo" <> Nothing -- Just "foo"
Nothing <> Just "bar" -- Just "bar"
Just "foo" <> Just "bar" -- Just "foobar"
Nothing <> Nothing -- Nothing
Я бы ожидал Nothing
для первых двух случаев.
Вот альтернативная реализация (надеюсь, это правильно / законно):
instance Semigroup a => Semigroup (Maybe a) where
Nothing <> _ = Nothing
_ <> Nothing = Nothing
Just a <> Just b = Just (a <> b)
instance Monoid a => Monoid (Maybe a) where
mempty = Just mempty
Я не хочу сказать, что эти альтернативные примеры лучше. Но они тоже кажутся полезными. Так почему же выбор был сделан в первую очередь вместо того, чтобы оставить реализацию пользователю?
2 ответа
Ваш экземпляр на самом деле является частным случаем гораздо более общего случая для аппликативных функторов.
newtype LiftA f a = LiftA { getLiftA :: f a }
instance (Applicative f, Semigroup a) => Semigroup (LiftA f a) where
LiftA x <> LiftA y = LiftA $ liftA2 (<>) x y
instance (Applicative f, Monoid a) => Monoid (LiftA f a) where
mempty = LiftA $ pure mempty
Я предполагал, что он будет где-то в стандартной библиотеке (возможно, под другим именем), но я не смог его найти. Но существование этого общего экземпляра может быть одной из причин, чтобы выбрать версию библиотеки Maybe
что больше Maybe
особая сила. С другой стороны, очень приятно, когда все ваши алгебраические структуры согласованы друг с другом; т.е. когда тип является Applicative
, использовать " LiftA
экземпляр стиля, когда это возможно (на всех классах F-алгебры).
С третьей стороны (!) Мы не можем иметь согласованность везде, так как экземпляр библиотеки согласен с Maybe
"s MonadPlus
пример. Это поразительно параллельно тому факту, что на натуральных числах есть два моноида: сложение и умножение. Для чисел мы просто выбрали не иметь моноидного экземпляра, потому что неясно, какой использовать.
В заключение я не знаю. Но, возможно, эта информация была полезной.
Я думаю, что идея этого заключается в том, что Maybe
должен иметь семантику, которая на самом деле только должным образом выражается Option
: он берет любую полугруппу и делает из нее моноид, добавляя Nothing
в качестве специального "свободного mempty". Т.е. на самом деле экземпляры должны быть
instance Semigroup a => Semigroup (Maybe a) where
Nothing <> a = a
a <> Nothing = a
Just x <> Just y = Just $ x <> y
instance Semigroup a => Monoid (Maybe a) where
mappend = (<>)
mempty = Nothing
Это аналогично тому, как []
дает свободный моноид над любым типом, добавив оба mempty
а также <>
,
Конечно, это требует, чтобы Semigroup
класс в base
, чего не было до недавнего времени.
Следствием этого является то, что mempty
становится явно сопоставимым с шаблоном, потому что параметрически не может зависеть от содержимого типа.
С практической точки зрения этот экземпляр, возможно, более полезен, чем тот, который вы предлагаете, потому что, как заметил Люк, он уже охватывается Applicative
экземпляр, так что вы можете легко написать liftA2 (<>)
а также pure mempty
, Конечно, стандартный экземпляр также уже охватывается Alternative
например, но Alternative/MonadPlus
всегда считался немного хакерским, уступающим более математически безупречному Semigroup
, Monoid
, Functor
а также Applicative
,