12

(+) 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.

L01man
  • 1,521
  • 10
  • 27
  • Theoretically, `+` and `*` are specialisations of `mappend` but in practice it they aren't: this idea is implemented as [thin wrappers around `Num`](http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-Monoid.html#g:3). (And it can't be sensibly implemented any other way, for one, both `+` and `*` are valid as the monoid operation and there would be no way to specific which one to use.) – huon Jun 09 '12 at 13:46
  • 3
    Numbers form a `Monoid` instance in two different ways - sum and product. There really are duplicated functions, which should be merged together (`map`, `fmap`, `liftM`, `liftA`; `(<*>)` `ap` and many others), but I don't think `mappend` (or `(<>)` in newer versions) is one of the functions that need a merge. – Vitus Jun 09 '12 at 13:47
  • What do you think of the edit of the first post? – L01man Jun 09 '12 at 14:18
  • I love your enthusiasm, but it would be a real shame to change the `Monoid` typeclass in the way you suggested: there are many types which are not monoids in two different (useful) ways. – Daniel Wagner Jun 09 '12 at 14:53
  • 3
    Numbers form a `Monoid` in many different ways. And if you add `mmultiply` to the `Monoid` class then it's no longer a monoid. As far as `(++)` and `mappend` they could indeed be the same. And in an older version of Haskell they were. The reason to keep `(++)` working on only lists was pedagogical; it was felt too many type classes for simple operations was difficult for beginners. That's also why `fmap` is not called `map`. – augustss Jun 09 '12 at 16:24
  • @DanielWagner: Yes, I understand it's not something Haskell specific and that there are many things around it that I don't even know the existence :o. – L01man Jun 09 '12 at 17:24
  • @augustss: I haven't touched Hugs yet, but I think it would be preferable to maintain `mappend` and `(++)` int it but to drop the former in the other implementations. Also, I'm not sure about the pedagogical use of keeping both: it makes explaining typeclasses harder, since the beginner will ask "Do I have to create duplicate functions for each of my instances?[...]Then why Haskell, the super clean language, does so?" and in a practical way that's not convenient at all... – L01man Jun 09 '12 at 17:29
  • @L01man, I am confused as to how `(.)` is in any way comparable to `fmap`? `(.)` takes two functions and returns their composition, while `fmap` takes a function and applies it inside a Functor. – Zoey Hewll Feb 02 '17 at 02:02

4 Answers4

11

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.

dflemstr
  • 25,947
  • 5
  • 70
  • 105
  • Thanks, I've a better understanding of the all thing now. I think I'll come back after I've learnt a little more maths :}. – L01man Jun 09 '12 at 17:19
  • It's actually unfortunate that all the operations in `Num` are in the same typeclass. For one thing it means the operations which should be well defined for `Complex Integer` can't be used! – Jeremy List Sep 30 '16 at 07:21
  • 1
    @deflemstr After reading your answer I have the impression you leave the question in the air about what makes ``Num`` instance so different to ``List``. I mean, you said that there are many possible ``mappend`` for ``List``, same applies for ``Num``, however one was choosen for ``List`` as default while no default was choosen for ``Num``. So what's wrong with having ``+`` as ``mappend`` for ``Num``? You can still have Multiplication with a newtype. – Gus Jan 03 '17 at 09:06
9

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.

dorchard
  • 1,198
  • 8
  • 15
4

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

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
3

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.

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • 5
    ...and `Product` and `Sum` aren't the only two possible monoids over numbers, either. They just happen to be the two most commonly used monoids. – Daniel Wagner Jun 09 '12 at 14:56
  • I just saw that Num is not an instance of Monoid. I find it odd to have monoids for "operations" and not for the numbers themselves. – L01man Jun 09 '12 at 15:38
  • @L01man: because then the question would become: what would the monoidal operations on a number be? Since there's no best answer to that question, we get the `newtype` wrappers instead. – Asherah Jun 11 '12 at 05:24
  • I'm aware of the problem but even though I don't know how to deal with it otherwise, I feel it is a bit awkward that Num is not a Monoid, [] is, Sum and Product are, but (++) is not. Wait... I think it finally makes sense, if we consider Sum, Product, [] and Maybe as boxes... Well, in `instance Monoid [a]`, `a` isn't a Monoid, as in `instance Monoid (Sum a)`. I was just confused that there was `(Num a)` for `Sum` and `Product` and not for `[]`. Then, are `(+)` and `(*)` the bad functions? – L01man Jun 11 '12 at 13:52