Почему экземпляр Полугруппы Maybe смещен в сторону Just, а Monoid использует Nothing в качестве пустого элемента? - PullRequest
0 голосов
/ 04 мая 2018

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 ]

0 голосов
/ 04 мая 2018

Я думаю, что идея этого заключается в том, что Maybe должен иметь семантику, которая на самом деле только должным образом выражается Option: она берет любую полугруппу и делает из нее моноид, добавив Nothing в качестве специального «free mempty». То есть на самом деле экземпляры должны быть

instance Semigroup a => Semigroup (Maybe a) where
  Nothing <> a = a
  a <> Nothing = a
  Just x <> Just y = Just $ x <> y

instance <b>Semigroup</b> a => Monoid (Maybe a) where
  mappend = (<>)
  mempty = Nothing

Это аналогично тому, как [] дает свободный моноид над любым типом, добавляя как mempty, так и <>.

Конечно, для этого требуется, чтобы класс Semigroup был в base, чего не было до недавнего времени.

Следствием этого является то, что mempty становится явно сопоставимым с шаблоном, поскольку по параметризации он не может зависеть от содержимого типа.

На практике этот экземпляр, возможно, более полезен, чем тот, который вы предлагаете, потому что, как заметил Люк, он уже охватывается экземпляром Applicative, поэтому вы можете легко написать liftA2 (<>) и pure mempty. Конечно, стандартный экземпляр также уже охватывается экземпляром Alternative, но Alternative/MonadPlus всегда считался немного хакерским, уступая более математически безупречным Semigroup, Monoid, Functor и Applicative .

0 голосов
/ 04 мая 2018

Ваш экземпляр на самом деле является частным случаем гораздо более общего случая для аппликативных функторов.

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 MonadPlus. Это поразительно параллельно тому факту, что на натуральных числах есть два моноида: сложение и умножение. Для чисел мы просто выбрали не иметь любой моноидный экземпляр, потому что неясно, какой использовать.

В заключение, я не знаю. Но, возможно, эта информация была полезна.

...