Suppose I have a type like
data Options = Options
{ _optionOne :: Maybe Integer
, _optionTwo :: Maybe Integer
, _optionThree :: Maybe String
} deriving Show
with many more fields. I would like to define a Monoid
instance for this type, for which the mempty
value is an Options
with all fields Nothing
. Is there a more concise way to write this than
instance Monoid Options where
mempty = Options Nothing Nothing Nothing
mappend = undefined
which would avoid the need to write a bunch of Nothing
s when my Options
has a ton more fields?
From HaskellWiki. In Haskell, the Monoid typeclass (not to be confused with Monad) is a class for types which have a single most natural operation for combining values, together with a value which doesn't do anything when you combine it with others (this is called the identity element).
One answer is that thinking in terms of monoids can be very beneficial to parallelism and efficient data structures. Using the Monoid interface in Haskell allows you to leverage the many convenient functions that work with them.
Strings, lists, and sequences are essentially the same monoid.
From HaskellWiki. The Semigroup represents a set with an associative binary operation. This makes a semigroup a superset of monoids. Semigoups have no other restrictions, and are a very general typeclass.
I would recommend just writing the Nothing
s, or even spelling out all the record fields explicitly, so you can be sure you don’t miss a case when adding new fields with a different mempty
value, or reordering fields:
mempty = Options
{ _optionOne = Nothing
, _optionTwo = Nothing
, _optionThree = Nothing
}
I haven’t tried it before, but it seems you can use the generic-deriving package for this purpose, as long as all the fields of your record are Monoid
s. You would add the following language pragma and imports:
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics (Generic)
import Generics.Deriving.Monoid
Add deriving (Generic)
to your data type and wrap all your non-Monoid
fields in a type from Data.Monoid
with the combining behaviour you want, such as First
, Last
, Sum
, or Product
:
data Options = Options
{ _optionOne :: Last Integer
, _optionTwo :: Last Integer
, _optionThree :: Maybe String
} deriving (Generic, Show)
Examples:
Last (Just 2) <> Last (Just 3)
= Last {getLast = Just 3}
First (Just 2) <> First (Just 3)
= First {getFirst = Just 2}
Sum 2 <> Sum 3
= Sum {getSum = 5}
Product 2 <> Product 3
= Product {getProduct = 6}
Then use the following function(s) from Generics.Deriving.Monoid
to make your default instance:
memptydefault :: (Generic a, Monoid' (Rep a)) => a
mappenddefault :: (Generic a, Monoid' (Rep a)) => a -> a -> a
In context:
instance Monoid Options where
mempty = memptydefault
mappend = ...
If the Monoid
instance for your record type follows naturally from the Monoid
instances of the record fields, then you could use Generics.Deriving.Monoid. The code could would look like this:
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
import Generics.Deriving.Monoid
data Options = { .. your options .. }
deriving (Show, Generic)
instance Monoid Options where
mempty = memptydefault
mappend = mappenddefault
Note that the record fields have to be Monoid
too, so you will have to wrap your Integer
s into Sum
or Product
(or possibly some other newtype
) depending on the exact behavior you want.
Then, assuming you want the resulting monoid to be synced with addition on top of Integer
and use the Sum
newtype, the resulting behavior would be:
> mempty :: Options
Options {_optionOne = Nothing, _optionTwo = Nothing, _optionThree = Nothing}
> Options (Just $ Sum 1) (Just $ Sum 2) (Just $ Sum 3) <> Options (Just $ Sum 1) (Just $ Sum 2) Nothing
Options {_optionOne = Just (Sum {getSum = 2}), _optionTwo = Just (Sum {getSum = 4}), _optionThree = Just (Sum {getSum = 3})}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With