## 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 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.