Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Which default monoid instance would you want?

I'm writing an extensible records library for fun and profit. I have two potential 'Monoid' instances I can give my 'HashRecord' type. Which would you prefer to see?

The record type is essentially HashRecord f xs, where xs is a type level list of (key :: Symbol) =: (value :: *) pairs, and f is a type constructor that each entry in the record is contained in.

Option 1

Delegate to the Monoid instance for the values.

mempty does pure mempty for each field in the record, and mappend does liftA2 mappend for the fields in the record.

>>> mempty :: HashRecord' Identity ["foo" =: String, "bar" =: Any]
fromList [("foo", "\"\""), ("bar", "Any { getAny = False }")]

instance Monoid (HashRecord' f '[]) where
    mempty = HashRecord mempty
    mappend a _ = a -- empty + empty = empty

instance
    ( Monoid val
    , Applicative f
    , MapEntry key val
    , Monoid (HashRecord' f xs)
    ) => Monoid (HashRecord' f (key =: val ': xs)) where
    mempty = -- insert key (pure mempty) (mempty :: HashRecord' f xs)
    mappend = -- Map.unionWith (liftA2 mappend)

Option 2:

Use the 'Alternative' instance for the functor.

mempty returns Alternative.empty for each entry in the field, and mappend does <|> over each entry.

>>> mempty :: HashRecord' Maybe ["foo" =: Int, "bar" =: Bool]
fromList [("foo", "Nothing"), ("bar", "Nothing")]

instance Monoid (HashRecord' f '[]) where
    mempty = HashRecord mempty
    mappend a _ = a -- empty + empty = empty
   
instance
    ( Alternative f
    , MapEntry key val
    , Monoid (HashRecord' f xs)
    ) => Monoid (HashRecord (key =: val ': xs)) where
    mempty = -- insert key Alternative.empty (mempty :: HashRecord xs)
    mappend = -- Map.unionWith (<|>)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.