Search code examples
haskellmonadsapplicativemonoids

Mappend (a1,b1) and (a2,b2) into (a1+a2, b1+b2)


I recall this is very basic, and could simply be done with pattern matching even through a lambda like (\a b -> (fst a + fst b, snd a + snd b) ) (1,2) (3,4). However, I think there should be ways provided by the Haskell standard library to do things like this. The definition of mappend for (a,b) type as Monoids looks quite similar. But doing the following didn't work:

(1,2) `mappend` (3,4)

Any Haskell way of adding two 2-tuples?


Solution

  • Numbers are monoids, that's not the problem. The problem is that there are two different, equally good ways in which they are monoids – addition is only one, the other is multiplication. The standard library thus decided to not give a monoid instance at all, which I think is a good decision.

    Haskell allows packing types in newtype wrappers to select an instance without affecting the underlying type. The two different monoid instances are available in base:

    Prelude Data.Monoid> case (Sum 1, Sum 2)<>(Sum 3, Sum 4) of (Sum a, Sum b) -> (a,b)
    (4,6)
    

    A bit awkward. There are a couple of ways in which you can make this more concise (I don't really recommend any of them; see bottom for a better solution). First, as Jon remarks in the comments, if the tuples contain just plain number literals like in your example, there's no need to explicitly wrap them in the Sum constructor, since there is an instance Num a => Num (Sum a) an number literals are polymorphic, i.e. the following will do:

    Prelude Data.Monoid> case (1,2) <> (3,4) of (Sum x, Sum y) -> (x,y)
    (4,6)
    

    however, if the types of the tuple elements are already fixed...

    Prelude Data.Monoid> let [a,b,c,d] = [1,2,3,4] :: [Int]
    Prelude Data.Monoid> case (a,b) <> (c,d) of (Sum x, Sum y) -> (x,y) :: (Int,Int) 
    <interactive>:6:25:
        Couldn't match expected type ‘Int’ with actual type ‘Sum Int’
        In the pattern: Sum x
        In the pattern: (Sum x, Sum y)
        In a case alternative: (Sum x, Sum y) -> (x, y) :: (Int, Int)
    

    Here you need to be explicit where the wrapping should happen. You can still briefen it a bit by using type-safe coercions, which allow you to wrap all the elements of an entire container (tuples, lists, maps, arrays...) in a newtype such as Sum in one go (and in O (1) time&space, which can also be quite a bonus).

    Prelude Data.Monoid Data.Coerce> case coerce (a,b) <> coerce (c,d) of (Sum x, Sum y) -> (x,y) :: (Int,Int)
    (4,6)
    

    Even briefer, and less reliant on local signatures, could be an approach using the Newtype class:

    Prelude Data.Monoid> :m +Control.Newtype
    Prelude Data.Monoid Control.Newtype> :m +Control.Arrow
    Prelude Data.Monoid Control.Newtype Control.Arrow> :simpleprompt 
    > ala (Sum***Sum) foldMap [(1,2), (3,4)]
    (4,6)
    

    ...however, as I was rather surprised to find out now, the newtype library doesn't come with the tuple instance that's necessary for this. You can define it yourself though:

    > :set -XFunctionalDependencies -XUndecidableInstances
    > instance (Newtype a α, Newtype b β) => Newtype (a,b) (α,β) where {pack=pack***pack; unpack=unpack***unpack}
    

    If you really only need the additive monoid, I recommend you instead use a dedicated class for addition: AdditiveGroup

    Prelude Data.AdditiveGroup> (1,2)^+^(3,4)
    (4,6)