Ticket #3339 (new proposal)

Opened 7 months ago

Last modified 5 months ago

Data.Monoid: Add (<>) as a synonym for mappend

Reported by: bos Owned by:
Component: libraries/base Version: 6.10.3
Keywords: Cc:
Operating System: Unknown/Multiple
Test Case: Architecture: Unknown/Multiple
Type of failure:

Description

This proposal was, I think, originally suggested by Jules Bean. The idea is to add two functions to the Data.Monoid module, (+>) and (<+), corresponding to different uses of mappend. These should not be methods of the Monoid typeclass, but top-level functions.

I hope (but slightly doubt) that the visual nature of the two operators might help to counter the thought that monoids are just for gluing things together.

(+>) :: (Monoid a) => a -> a -> a
a +> b = a `mappend` b

(<+) :: (Monoid a) => a -> a -> a
a <+ b = b `mappend` a

infixl 4 +>
infixl 4 <+

Proposed deadline: two weeks.

If this looks reasonable, I'll attach darcs patches.

Change History

  Changed 7 months ago by duncan

I dislike the asymmetry. The ++ operator is not commutative but we don't artificially make it visually asymmetric. Why not take Ross's proposal and use <> as in Data.Sequence.

For the sake of the discussion, perhaps someone should also explain why we cannot just generalise the type of ++. There's a separate argument that we should not.

Also, what is the purpose of the flipped append? Why do we want to dedicate a symbol for flipped append? I use monoid operations a lot and I cannot think of a single use of flipped append.

  Changed 7 months ago by ajd

What is wrong with generalizing the type of (++)? If it would not break too much code (on first glance, I'm not sure how it would), I think that that would be the best solution.

  Changed 7 months ago by jochemb

Generalizing (++) may be confusing for newbies, but I think this is the best way to go.

(+>) and (<+) are totally asymmetric, like (>>=) and (*>), whereas the type of (+>) is Monoid a => a -> a -> a

(++) is (pretty obviously) not commutative, like (>>) and (&&), where there can be no confusion.

  Changed 7 months ago by david48

In my opinion as a newbie, there are far more confusing things for newbies than (++) being the mappend of monoids. And anyways, monoids is far from being the hardest thing to understand in haskell, once you read a few well written blog posts.

You can still learn to use ++ as the concatenation operator for strings, and discover later that there is more to it than it looks.

I support generalizing the type of (++)

  Changed 7 months ago by JulesBean

There is no universal convention that symmetric-looking operators should be commutative.

However, it is certainly a handy visual guide.

To address specific points:

(&&) looks commutative to me, and it *is* commutative, in the _|_ free fragment of haskell. That's the fragment I most commonly reason about. You don't get many algebraic laws without that. Similarly ().

(>>) looks uncommutative - it's not about repeated symbols, it's about the way the > symbols obviously 'point' in one direction. And, (>>) *is* noncommutative, of course.

Concrete advantages for (+>):

  • It has a symbol which visually indicates direction, and mappend is often a directional operation in some sense;
  • It can be 'naturally' flipped to yield <+; it's useful to have a short name for flip mappend.

The latter is the key point. The reason to use +> is so we can use <+.

I rejected using (++) not just because it looks too symmetric (that's not a big problem, it doesn't confuse people too much) but simply because the backwards-compatibility damage of changing the type of a very very widely used operator is very significant.

By contrast, (+>) is not used in any widely used libraries, certainly not in base, and the backwards-compatibility damage of adding a new symbol to Data.Monoid is relatively low. No worse than adding any other new function to an API.

  Changed 7 months ago by duncan

Is there any evidence for why a flipped mappend is useful?

  Changed 7 months ago by JulesBean

I think it's useful for Endo a, where you sometimes want to postcompose and sometimes precompose functions.

I think it's useful for any monoid which represents something like the composition of a graphical image, where precomposition is putting something 'underneath' and postcomposition is putting something 'on top'.

I suspect you quite often get Monoids along those general lines in DSLs; I've certainly seen a view.

Of course the case for a special flipped operator can never be that strong; you can always just write an expression the other way around.

  Changed 7 months ago by bkomuves

+1 vote for generalizing (++). I think flipped mappend is so rare that writing "flip (++)" is actually easier to read. And you can still define your own <+ and +> if you use them a lot.

  Changed 7 months ago by tibbe

I agree with generalizing (++). Are there any programs that will break because of that generalization? My argument for reusing (++) is that I'd like to keep the number of different operators I have to memorize (in particular when reading other people's code) to a minimum. I only think very fundamental type classes deserve operators (e.g. Monad, Functor, Monoid, Num).

  Changed 7 months ago by jeltsch

I agree. Since (++) is mappend for a specific type, it makes much sense to just generalize (++) to work with arbitrary Monoid instances. There is also a poll about this topic which has a strong bias to making (++) the new mappend:  http://doodle.com/4yrfd7qaw5man3rm.

follow-up: ↓ 12   Changed 7 months ago by JulesBean

Technical decisions are not best made by democracy. A poll is not interesting.

What is interesting is arguments of substance. (++) is a sensible choice but has backward-compatibility problems, and "confusing newbies with weird error messages" problems. It is only for those reasons that I didn't choose it.

If we're choosing something new, then +> has the advantage of being visually flippable, but <> has the advantage of being already used in (at least) two other monoids.

in reply to: ↑ 11   Changed 7 months ago by jeltsch

What are the exact backward-compatibility problems? I can only think of cases like

cat = (++)

which only matter because the monomorphism restriction is still in use.

The “confusing newbiews with weird error messages” problem is already present in several places. For example, the expression 1 + 'A' will make GHC complain that there is no instance for (Num Char). I think, it would be better to try to generate better error messages instead of increasing the number of operators.

  Changed 7 months ago by igloo

  • difficulty set to Unknown
  • type changed from bug to proposal
  • milestone set to Not GHC

  Changed 5 months ago by bos

  • summary changed from Data.Monoid: Add (+>) as a synonym for mappend to Data.Monoid: Add (<>) as a synonym for mappend

The revised proposal is now as follows:

Use <>, with fixity of infixr 6 <>.

The change would also involve updating the pretty-print library to use the monoidal version of this operator.

Let's wrap this up in two weeks, this time for sure :-)

Note: See TracTickets for help on using tickets.