Batou99
Batou99

Reputation: 929

Automatic propagation of monoidal mappend

Let's say I have a monoid defined as this:

data TotalLine = TotalLine { totalQuantity :: Int, orderTotal :: Float }

instance Monoid TotalLine where
  mempty = zero
  mappend = add

As totalQuantity and orderTotal are both numbers they also form a monoid under (+) Is there any way to define

add :: TotalLine -> TotalLine -> TotalLine

so, Can I just propagate the mappend call on each field instead of having to manually define something like

add line1 line2 =
  TotalLine {
    totalQuantity = totalQuantity line1 + totalQuantity line2,
    orderTotal = orderTotal line1 + orderTotal line2
   }

Upvotes: 3

Views: 101

Answers (3)

zakyggaps
zakyggaps

Reputation: 3080

There's generic-deriving package matches exactly your requirement:

{-# LANGUAGE DeriveGeneric #-}

import           Generics.Deriving.Monoid
import           GHC.Generics

data T = T {str :: String, str' :: String}
  deriving (Generic, Show)

main = undefined

instance Monoid T where
  mempty = memptydefault
  mappend = mappenddefault

And some result from ghci:

> T "a" "b" `mappend` T "c" "d"
< T {str = "ac", str' = "bd"}

But it doesn't work out-of-box for your TotalLine data type, since both Int and Float do not have an instance of Monoid.

Also add a dependency just for that purpose is not so economic. You'd better implement the Monoid instance manually.


There has been some discussion on why GHC cannot derive Monoid instances, it turned out in general such derived instance may not be unique. But in the special case when the data type have only one constructor with concrete and monoidal fields the derived instance is unique.

Upvotes: 4

Christopher King
Christopher King

Reputation: 10941

If you're fine changing the type a little (but that is still isomorphic to yours), you could try this:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Data.Monoid

newtype TotalLine = TotalLine (Sum Int, Sum Float) deriving Monoid

Upvotes: 3

d8d0d65b3f7cf42
d8d0d65b3f7cf42

Reputation: 2653

Note there are several monoids with domain Int (Nat, Float, ..) so I would find it highly confusing to read code that has instance Monoid Int. Then mappend could be any of plus, times, max, min, and a lot more.

Upvotes: 7

Related Questions