Почему бездельник оценивает содержание моноида
Почему
(mempty :: String)
работать, но
(mempty :: Maybe Bool)
выдает ошибку Bool
не имеющий тип класса Monoid
, но почему это даже имеет значение, это завернуто в Может быть, так или иначе?
2 ответа
Для списков, [a]
это Monoid
несмотря ни на что:
instance {- no requirements on a here! => -} Monoid [a] where
...
Так String
является моноидом без дальнейшей проверки свойств Char
, Но Maybe a
это только пример Monoid
если a
это:
instance Monoid a => Monoid (Maybe a) where
...
Таким образом, для Maybe Bool
чтобы быть моноидом, мы должны проверить, что Bool
это моноид Это полностью объясняет ваше сообщение об ошибке.
Дополнительный вопрос номер один: почему a
должен быть Monoid
? Это так, что мы можем написать mappend
:
Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
Конечно, вам действительно нужно только Semigroup
для этого; но эта тонкая разница не имеет значения, потому что Bool
не является Semigroup
, или.
Второй вопрос: почему нет Bool
Monoid
? Потому что есть несколько хороших примеров, и непонятно, что "благословить" как официальный пример. Таким образом, экземпляры прикреплены к newtype
с над Bool
; два наиболее распространенных Any
а также All
,
Дополнительный вопрос номер три: каковы ваши альтернативы? Вы могли бы First
или же Last
для моноидов, которые держат первый (соответственно последний) Just
они видят. Существует также стандартный экземпляр и экземпляр, где mempty = pure mempty; mappend = liftA2 mappend
хотя я не знаю стандартного места для этого.
Это Monoid
экземпляр для Maybe
:
instance Monoid a => Monoid (Maybe a) where
mempty = Nothing
Nothing `mappend` m = m
m `mappend` Nothing = m
Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
Требуется, чтобы базовый тип имел Monoid
экземпляр, потому что его собственный mappend
делегаты к тому из базового типа. Обратите внимание, что это Monoid
ограничение является излишне ограничительным, так как основной mempty
на самом деле не используется. Более уместный взгляд на это предоставляется как Option
в Data.Semigroup
(а Semigroup
это Monoid
без mempty
):
-- Paraphrasing the relevant bits of the source.
instance Semigroup a => Semigroup (Maybe a) where
Nothing <> b = b
a <> Nothing = a
Just a <> Just b = Just (a <> b)
newtype Option a = Option { getOption :: Maybe a }
instance Semigroup a => Semigroup (Option a) where
Option ma <> Option mb = Option (ma <> mb)
instance Semigroup a => Monoid (Option a) where
mempty = Option Nothing
mappend = (<>)
Если вы хотите Monoid
за Maybe
который не заботится о базовом типе, взгляните на упаковщики нового типа, предоставленные Data.Monoid
: First
(или же Alt Maybe
) для левостороннего и Last
(или же Dual (Alt Maybe)
) для предвзятого.
-- For instance, this is the First monoid:
newtype First a = First { getFirst :: Maybe a }
instance Monoid (First a) where
mempty = First Nothing
First Nothing `mappend` r = r
l `mappend` _ = l
GHCi> import Data.Monoid
GHCi> First Nothing <> First Nothing
First {getFirst = Nothing}
GHCi> First (Just 3) <> First Nothing
First {getFirst = Just 3}
GHCi> First Nothing <> First (Just 3)
First {getFirst = Just 3}
GHCi> First (Just 3) <> First (Just 4)
First {getFirst = Just 3}
GHCi> Last (Just 3) <> Last (Just 4)
Last {getLast = Just 4}
GHCi> Alt (Just 3) <> Alt (Just 4)
Alt {getAlt = Just 3}
GHCi> Dual (Alt (Just 3)) <> Dual (Alt (Just 4))
Dual {getDual = Alt {getAlt = Just 4}}
(Кстати, Alt
делегаты mempty
а также mappend
к Alternative
экземпляр Maybe
, что эквивалентно тому, что Алек предлагает в комментарии.)