Can TimeDuration type be an instance of Semigroup and Monoid?

haskell semigroup example
monoid laws
function monoid
tree monoid

In an attempt to find a quick and easy way to add different time periods, it occurred to me that I could declare them instances of Semigroup and Monoid. Are the following instantiations valid?

data TimeDuration = S Int | M Int | H Int deriving (Show, Eq)

instance Semigroup TimeDuration where
    S n1 <> S n2 = S (n1 + n2)
    M n1 <> M n2 = S (60 * (n1 + n2))
    H n1 <> H n2 = S (3600 * (n1 + n2))
    S n1 <> M n2 = S (n1 + 60 * n2)
    S n1 <> H n2 = S (n1 + 3600 * n2)
    M n1 <> S n2 = S (60 * n1 + n2)
    M n1 <> H n2 = S (60 * n1 + 3600 * n2)
    H n1 <> S n2 = S (3600 * n1 + n2)
    H n1 <> M n2 = S (3600 * n1 + 60 * n2)

instance Monoid TimeDuration where
    mempty = S 0

Example: mconcat [S 1, M 2, M 3, S 2, H 1] == S 3903

User leftaroundabout ask me to produce more significative example. So this is a new implementation and some examples that I hope will show better the possible variety of the results of the operation (<>)

instance Semigroup TimeDuration where
    S n1 <> S n2 = S (n1 + n2)
    M n1 <> M n2 = M (n1 + n2)
    H n1 <> H n2 = H (n1 + n2)
    S n1 <> M n2 = S (n1 + 60 * n2)
    M n1 <> S n2 = S n2 <> M n1
    S n1 <> H n2 = S (n1 + 3600 * n2)
    H n1 <> S n2 = S n2 <> H n1
    M n1 <> H n2 = M (n1 + 60 * n2)
    H n1 <> M n2 = M n1 <> H n1 


instance Monoid TimeDuration where
    mempty = S 0
    mconcat [] = S 0
    mconcat xs = foldr1 (\y acc -> y <> acc) xs

-- ex. mconcat [M 2, M 3] == M 5
-- ex. mconcat [H 2, H 3] == H 5
-- ex. mconcat [M 2, M 3, S 1] == S 301
-- ex. mconcat [H 2, H 3, M 1] == M 301
-- ex. mconcat [H 2, H 3, S 1] == S 18001

Yeah, that's fine. It's basically equivalent to

{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}

import Data.AdditiveGroup
import GHC.Generics

newtype TimeDuration = TimeDuration {getDurationInSeconds :: Integer}
  deriving (Generic, AdditiveGroup)

...with extra constructors for the special cases TimeDuration 60, TimeDuration 180... TimeDuration 3600 etc. (which in your construction are actually redundant; you could better make simple builder-functions to do the same job).

The AdditiveGroup typeclass is a specialised monoid typeclass, that avoids any confusion that it might be a multiplication monoid instead – which wouldn't actually make any sense because dimension-wise, time × time doesn't match time, but for dimensionless number types like Int or Double the multiplication monoid is just as sensible as the addition one.

Because these still have an unambiguous AdditiveGroup instance, the AdditiveGroup TimeDuration instance can just be derived. It could also be written by hand

instance AdditiveGroup TimeDuration where
  zeroV = TimeDuration 0
  TimeDuration δt₀ ^+^ TimeDuration δt₁ = TimeDuration $ δt₀ + δt₁

One particularly nice thing about AdditiveGroup: it's a precursor to VectorSpace which gives you a multiplication that does make sense, namely

instance VectorSpace TimeDuration where
  type Scalar TimeDuration = Integer
  factor *^ TimeDuration δt = TimeDuration $ factor * δt

Can TimeDuration type be an instance of Semigroup and Monoid?, In an attempt to find a quick and easy way to add different time periods, it occurred to me that I could declare them instances of Semigroup and Monoid. Are the  Any datatype a which has an associative binary operation will be able to become a member of the Semigroup typeclass. An instance of Monoid a automatically satisfies the requirements of a Semigroup making Semigroup a strict superset of Monoid.


If we are writing an instance for a class with laws, the first sanity check should, in general, be whether the instance follows them. For Semigroup, there is the associativity law...

(x <> y) <> z = x <> (y <> z)

... while Monoid adds the identity laws:

mempty <> x = x
x <> mempty = x

Your instance follows the laws as long as only S values are involved (as it boils down to adding seconds). However, the identity laws are broken once the other constructors are brought into play, as in:

mempty <> H 2 = S 7200

We might be tempted to argue that H 2 and S 7200 are morally the same, and the difference between them is merely a presentation issue (cf. how show . read is, strictly speaking, not id as it normalises formatting). The question, then, would become why are H and M necessary if they aren't meant to be used for any relevant distinctions (cf. the other answers).

Data.Semigroup, This makes a semigroup a superset of monoids. Therefore <> could be defined as + or * for instances of class Num a . Division Take a nonempty list of type a and apply the <> operation to all of them to get a single result. A semigroup generalizes a monoid in that there might not exist an identity element. It also (originally) generalized a group (a monoid with all inverses) to a type where every element did not have to have an inverse, thus the name semigroup.


This looks ok but why not just do this?

newtype TimeDuration = Seconds (Sum Integer) deriving (Show, Eq, Monoid, Semigroup)

I think you need extensions to derive semigroup and monoid through the newtype, and you need to import Sum from Data.Foldable

You can have functions for the rest:

seconds = Seconds
minutes = seconds . (*60)
hours = minutes . (*60)

Another thing you might want would be:

data TimDuration = Duration { seconds :: Int, minutes :: Int, hours :: Integer }

And define a function to normalise this. But here’s a question to demonstrate that actually adding durations and times is difficult: is adding a minute the same as adding 60s? One would think so but what if say the time is 23:59:00 and the last minute has a leap second? In such a case if you add 60 seconds you get to 23:59:60. If you add a minute should you get 23:59:60 or 00:00:00 for the next day? In this case I think saying a minute is 60s is sensible. But how long is a day or a month? What happens if you add a day to 12:00 the day before the clocks change. Should you get 12:00 the next day or 13:00/11:00? And if you add a month to 29 January should you get 28 February or 1 March? And what about on leap years? Time is hard once things aren’t always seconds.

Monoid, In Haskell, the Monoid typeclass (not to be confused with Monad) is a class and indeed you can think of a Monoid instance declaration for a type m as Semigroup m => Monoid m where mempty :: m -- defining mappend is  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).


purescript-datetime/Duration.purs at master · purescript/purescript , instance semigroupMilliseconds :: Semigroup Milliseconds where. append (​Milliseconds x) (Milliseconds y) = Milliseconds (x + y). instance monoidMilliseconds  Semigroup. If a type A can form a Semigroup it has an associative binary operation.


Lesson 17. Design by composition—Semigroups and Monoids, You can think of <> as an operator for combining instances of the same type. You can trivially implement Semigroup for Integer by defining <> as + . Listing 17.2.


Monoid, Integers, for example, are monoids under both a “sum” and a “product” operation. to have a Monoid instance, a type must also have a Semigroup instance. an identity value of some kind will be needed but the type cannot yet be known.