Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: duplicated functions (+) and (++), mappend

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

like image 708
L01man Avatar asked Jun 09 '12 13:06

L01man


1 Answers

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.

like image 82
dflemstr Avatar answered Oct 15 '22 05:10

dflemstr