(+)
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With