Question

(+) and (++) are just specializations of mappend; am I right? Why are they needed? This is useless duplication since Haskell has these powerful typeclasses and type inference. Let's say we delete (+) and (++) and rename mappend (+) for visual convenience and typing gain. Coding would be more intuitive, shorter and more understandable for beginners:

--old and new
1 + 2
--result
3

--old
"Hello" ++ " " ++ "World"
--new
"Hello" + " " + "World"
--result
"Hello World"

--old
Just [1, 2, 3] `mappend` Just [4..6]
--new
Just [1, 2, 3] + Just [4..6]
--result
Just [1, 2, 3, 4, 5, 6]

(It makes me dream.). Three, and maybe more, functions for the same thing isn't a good thing for a beautiful language who insists on abstraction and stuff such as Haskell. I also saw the same kind of repetitions with monads: fmap is the same, or nearly, as map, (.), liftM, mapM, forM, ... I know there are historial reasons for fmap, but what about monoids? Is the Haskell commitee planning something about this? It would break some codes, but I heard, though I'm not sure, there's an incoming version which will have great changes, which is a great occasion. It's too much a pity... At least, is a fork affordable?

EDIT In answers I read, there is the fact that for numbers, either (*) or (+) could fit in mappend. In fact, I think that (*) should be part of Monoid! Look:

Currently, forgeting about the functions mempty and mconcat, we only have mappend.

class Monoid m where
    mappend :: m -> m -> m

But we could do that:

class Monoid m where
    mappend :: m -> m -> m
    mmultiply :: m -> m -> m

It would (maybe, I haven't though enough about it yet) behave as follows:

3 * 3
mempty + 3 + 3 + 3
0 + 3 + 3 + 3
9

Just 3 * Just 4
Just (3 * 4)
Just (3 + 3 + 3 +3)
Just 12

[1, 2, 3] * [10, 20, 30]
[1 * 10, 2 * 10, 3 * 10, ...]
[10, 20, 30, 20, 40, 60, ...]

Actually 'mmultiply' would just be defined only in terms of 'mappend' so for instances of Monoid there is no need to redefine it! Then Monoid is closer to mathematics; maybe we could as well add (-) and (/) to the class! If this works, I think it would solve the case of Sum and Product as well as the functions duplication: mappend becomes (+) and the new mmultiply is just (*). Basically I suggest a refactoring of code with a "pull up". Oh, we would need as well a new mempty for (*). We could abstract these operators in a class MonoidOperator and define Monoid as follows:

class (Monoid m) => MonoidOperator mo m where
    mempty :: m
    mappend :: m -> m -> m

instance MonoidOperator (+) m where
    mempty = 0
    mappend = --definition of (+)

instance MonoidOperator (*) where
    --...

class Monoid m where
    -...

Well I don't know how to do this yet but I think there is a cool solution for all of this.

Was it helpful?

Solution

You are trying to mix somewhat separate concepts here.

Arithmetic and list concatenation are very practical, direct operations. If you write:

[1, 2] ++ [3, 4]

...you know that you will get [1, 2, 3, 4] as the result.


A Monoid is a mathematical algebraic concept that is on a more abstract level. This means that mappend doesn't have to literally mean "append this to that;" it can have many other meanings. When you write:

[1, 2] `mappend` [3, 4]

...these are some valid results that that operation might produce:

[1, 2, 3, 4] -- concatenation, mempty is []

[4, 6]       -- vector addition with truncation, mempty is [0,0..]

[3, 6, 4, 8] -- some inner product, mempty is [1]

[3, 4, 6, 8] -- the cartesian product, mempty is [1]

[3, 4, 1, 2] -- flipped concatenation, mempty is []

[]           -- treating lists like `Maybe a`, and letting lists that
             -- begin with positive numbers be `Just`s and other lists
             -- be `Nothing`s, mempty is []

Why does mappend for lists just concatenate the lists? Because that's simply the definition for monoids that the guys who wrote the Haskell Report chose as the default implementation, probably because it makes sense for all element types of a list. And indeed, you can use an alternative Monoid instance for lists by wrapping them in various newtypes; there is for example an alternative Monoid instance for lists that performs the cartesian product on them.

The concept of "Monoid" has a fixed meaning and a long history in mathematics, and changing its definition in Haskell would mean diverging from the mathematical concept, which should not happen. A Monoid is not simply a description of an empty element and a (literal) append/concatenation operation; it is the foundation for a broad range of concepts that adhere to the interface that Monoid provides.


The concept that you are looking for is specific to numbers (because you could not define something like mmultiply or maybe mproduce/mproduct for all instances of Maybe a for example), a concept that already exists and is called Semiring in mathematics (Well, you didn't really cover associativity in your question, but you're jumping between different concepts in your examples anyways﹘sometimes adhering to associativity, sometimes not﹘but the general idea is the same).

There are already implementations of Semirings in Haskell, for example in the algebra package.

However, a Monoid is not generally a Semiring, and there are also multiple implementations of Semirings for real numbers besides addition and multiplication, in particular. Adding broad generalized additions to very well-defined type classes like Monoid should not be done just because it "would be neat" or "would save a few keystrokes;" there is a reason why we have (++), (+) and mappend as separate concepts, because they represent entirely different computational ideas.

OTHER TIPS

On renaming mappend to (+)/(*)

Whilst (+) and (*) are both monoids they have additional distributivity laws, relating the two operations, as well as cancellative laws e.g. 0*x = 0. Essentially, (+) and (*) form a ring. Two monoids on some other type may not satisfy these ring (or even weaker semi-ring) properties. The naming of the operators (+) and (*) is suggestive of their additional (inter-related) properties. Thus, I would avoid subverting traditional mathematical intuitions by renaming mappend to + or * as these names suggest additional properties which may not hold. Sometimes too much overloading (i.e. too much generalisation) leads to a loss of intuition and thus a loss of usability.

If you do happen to have two monoids which form some kind of ring then you might like to derive an instance of Num from these, as the names "+" and "*" suggest the additional properties.

On conflating (++) and mappend

Renaming mappend to (++) might be more appropriate as there is less additional mental baggage with (++). Indeed, since lists are the free monoid (that is, a monoid with no additional properties) then using the traditional list-concatentation operator (++) to denote the binary operation of a monoid does not seem like a terrible idea.

On defining multiple monoids for one type

As you point out, both (+) are (*) are monoids but both cannot be made an instance of Monoid for the same type t. One solution, which you are half-provide, is to have an additional type parameter to the Monoid class to distinguish two monoids. Note that type classes may only be parameterised by types, not by expression as you show in your question. A suitable definition would be something like:

class Monoid m variant where
 mappend :: variant -> m -> m -> m
 mempty :: variant -> m

data Plus = Plus
data Times = Times

instance Monoid Int Plus where
    mappend Plus x y = x `intPlus` y
    mempty = 0

instance Monoid Int Times where
    mappend Times x y = x `intTimes` y
    mempty = 1

(+) = mappend Plus
(*) = mappend Times 

In order for applications of mappend/mempty to be resolved to a particular operation, each must take a value witnessing the type that indicates the particular monoid "variant".

Aside: the title of your question mentions mconcat. This is an entirely different operation to mappend- mconcat is the monoid homomorphism from the free monoid to some other monoid i.e. replace cons with mappend and nil with mempty.

If you take a look on Hackage, you'll find many alternative Prelude implementations aimed at fixing these problems.

Well there are two Monoids for numbers - Product and Sum, how would you deal with that?

Three, and maybe more, functions for the same thing isn't a good thing for a beautiful language who insists on abstraction and stuff such as Haskell.

Abstractions are not about eliminating code duplication. Arithmetic and Monoid operations are two different ideas, and despite having cases where they share the same semantics you do not gain anything by merging them.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top