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 Nothings 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 Nothings, 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 Monoids. 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 Integers 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