Monoids, Applicatives and Tuples

In todays Advent of Code I found it useful to use monoids, however I have a few questions on how to best use them when I want to deal with multiple monoids at the same time.

For example (assume there’s a function that returns “triple” tuples of monoids):

ghci> :m + Control.Applicative
ghci> :m + Data.Monoid
ghci> let a = (Sum 2, All True, Product 1)
ghci> let b = (Sum 3, All False, Product 10)

Playing around in GHCi I have the following:

ghci> a <* b
(Sum {getSum = 5},All {getAll = False},Product {getProduct = 1})

Last element of the tuple isn’t applied, I can work around that (in lack of clear understanding or solution) with an extra value, using unit just for example

ghci> a <* b
(Sum {getSum = 5},All {getAll = False},Product {getProduct = 10},())

Question 1 is there a way for me to workaround this behaviour?

Question 2 if I have a list of these tuples, how do I reduce them down to one value?

ghci> _what_function [a, b, a, b]
1 Like

You shouldn’t use applicatives for this. Just use a <> b. For foldable structures you can use fold from Data.Foldable or you can use mconcat for lists specifically.

The reason applicative almost works is mostly a coincidence. It is a combination of the infamous tuple instance, which is biased to the last element of the tuple, and the fact that all the rest is combined monoidally.

The applicative instance you were using is this one:

instance (Monoid a, Monoid b) => Applicative ((,,) a b)

This may be a bit confusing because the (,,) is now using prefix notation and it is missing the last element of the tuple, but that’s the form that the Applicative class expects. And the <* function for this instance has the type:

(<*) :: (Monoid a, Monoid b) => (a, b, c) -> (a, b, d) -> (a, b, c) 

Here you can see explicitly that the d is not used in the output.

2 Likes

Thanks. I’m going to use foldMap as it’s one less import away (part of Prelude) and I think I will generally map things into these tuples before reducing them down to one value.

I assumed that for tuples I had to go the applicative route, as I haven’t used their Monoid instance before (wasn’t aware they had one).

@mhitza I’d suggest to declare a record type data X = X (Sum Int, All Bool, Product Int), write an explicit Monoid and Semigroup instance and just use foldMap on a list of such as you already know.

Edit: you don’t even need to write your instances and can go the generic route : Data.Semigroup.Generic

Thanks for the suggestion. I’ll definitely keep your suggestion in mind, and have done something along those lines in AoC day 2.

I haven’t kept up to date with Haskell and only learned during the challenge that I have to define Semigroup and Monoid now. Semigroup Max would have been useful to what I’ve coded but since it isn’t a Monoid (no identity element) I did not find about it till later, and also wouldn’t be able to use it with foldMap.

DerivingVia might be an ergonomic approach, but wouldn’t be something I could come up with on my own.

Note that if you have just a Semigroup a, you have a Monoid (Maybe a) even if a alone isn’t a monoid (Nothing takes on the role of the neutral element). So you can foldMap with a Maybe (Max Int) and fromMaybe the result with whatever you want to happen if there are no inputs.

1 Like

I agree with @jaror.

I would use a <> b (or mappend a b), and mconcat [a, b, a, b].

These days, you don’t even need a separate package:

{-# LANGUAGE DeriveGeneric      #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia        #-}

import GHC.Generics (Generic)

data V4 a = V4 a a a a
  deriving stock Generic

  deriving (Semigroup, Monoid)
  via Generically (V4 a)

But if you want to support older GHCs, you can use the generically: Generically newtype to use with DerivingVia compatibility package.

1 Like