Opened 11 years ago

Closed 8 years ago

#3339 closed feature request (fixed)

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

Reported by: bos Owned by: igloo
Priority: normal Milestone: 7.4.1
Component: libraries/base Version: 7.3
Keywords: Cc: merehap@…, gale@…
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

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.

Attachments (1)

0001-Add-as-an-alias-for-mappend.patch (682 bytes) - added by tibbe 8 years ago.

Download all attachments as: .zip

Change History (45)

comment:1 Changed 11 years 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.

comment:2 Changed 11 years 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.

comment:3 Changed 11 years 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.

comment:4 Changed 11 years 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 (++)

comment:5 Changed 11 years 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.

comment:6 Changed 11 years ago by duncan

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

comment:7 Changed 11 years 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.

comment:8 Changed 11 years 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.

comment:9 Changed 11 years 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).

comment:10 Changed 11 years 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.

comment:11 Changed 11 years 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.

comment:12 in reply to:  11 Changed 11 years 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.

comment:13 Changed 11 years ago by igloo

difficulty: Unknown
Milestone: Not GHC
Type: bugproposal

comment:14 Changed 10 years ago by bos

Summary: Data.Monoid: Add (+>) as a synonym for mappendData.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 :-)

comment:15 Changed 10 years ago by merehap

Cc: merehap@… added
Type of failure: None/Unknown

comment:16 Changed 10 years ago by igloo

Resolution: wontfix
Status: newclosed

Looks like an abandoned proposal. If that's not the case, please re-open and give a discussion summary and consensus decision, as described on the process page.

comment:17 Changed 9 years ago by jeltsch

Resolution: wontfix
Status: closednew

I don’t think that this proposal was abandoned. I suppose that just nobody made the next steps to make the proposed change become reality. Would anyone object if I’d declare “use <> with fixity infixr 6 <>” as the consensus and go on with producing a patch for the libraries, etc.?

comment:18 Changed 9 years ago by dons

I thought the use of <> had been agreed upon. Duncan just gave a talk claiming this would be in the next base library release.

+1 for <>. Let's kill confusing hints of sequences in monoid.

comment:19 Changed 9 years ago by tibbe

+1

Having a right associative operator for monoid makes it less error prone to use builder monoid like Data.Binary.Builder and Data.Text.Lazy.Builder as they should be used in a right associative way.

I think what needs to happen is that someone creates a patch and moves the ticket to the "please merge" stage, assuming that consensus was reached (I didn't check the discussion thread).

comment:20 Changed 9 years ago by igloo

It should be "patch" rather than "please merge" (which is for merging from HEAD to STABLE branch). I've just updated http://www.haskell.org/haskellwiki/Library_submissions to say that.

Also, please link to the start of the thread on the mailing list.

comment:21 Changed 9 years ago by nominolo

The discussion for this proposal can be found at: http://thread.gmane.org/gmane.comp.lang.haskell.libraries/11450

We were approaching consensus for the addition of of:

infixr 6 <>

(<>) :: Monoid m => m -> m -> m
(<>) = mappend

and a matching change for (<+>) in the pretty package.

comment:22 Changed 9 years ago by nominolo

Benchmarks checking whether changing associativity of (<>) in pretty would lead to performance issues:

import Text.PrettyPrint

import Data.List
import Criterion.Main


f_left :: Int -> Doc
f_left n = foldl' (<>) empty (map (text . show) [10001..10000+n])

f_right :: Int -> Doc
f_right n = foldr (<>) empty (map (text . show) [10001..10000+n])

main =
  defaultMain $ 
    [ bench "left"     $ nf (length . render . f_left)  10000
    , bench "right"    $ nf (length . render . f_right) 10000
    , bench "left20k"  $ nf (length . render . f_left)  20000
    , bench "right20k" $ nf (length . render . f_right) 20000
    , bench "left30k"  $ nf (length . render . f_left)  30000
    , bench "right30k" $ nf (length . render . f_right) 30000
    ]

Results (lower numbers are better):

       Iterations
      _10K__________20K__________30K________
Left   10.1 (0.5)   24.4 (0.6)   40.0 (1.3)
Right   8.9 (0.2)   22.7 (3.1)   31.2 (4.6)

Format:  runtime (stddev) all in milliseconds

So switching to right-associativity may actually improve performance slightly. Scaling doesn't seem to be quite linear, but that could be due to cache effects or suchlike; and it's also outside the scope of this proposal.

comment:23 in reply to:  21 ; Changed 9 years ago by jeltsch

It was also suggested to make (<>) a method of Monoid and insert the following default definitions:

(<>) = mappend
mappend = (<>)

Any objections against this?

comment:24 in reply to:  23 Changed 9 years ago by jeltsch

In the long run, we should probably get rid of mappend altogether.

comment:25 Changed 9 years ago by nominolo

Getting rid of mappend would break a lot of packages, so that will take a while. Also, what should happen to mconcat? The naming of mappend makes somewhat sense, because [a] is the free Monoid, so mconcat is just the extension of that naming strategy. Should mconcat stay as is, or can anyone think of a better name?

comment:26 in reply to:  25 Changed 9 years ago by jeltsch

Replying to nominolo:

Getting rid of mappend would break a lot of packages, so that will take a while.

Of course. But that’s why I think we should start to prepare for mappend removal now. ;-)

Also, what should happen to mconcat? The naming of mappend makes somewhat sense, because [a] is the free Monoid, so mconcat is just the extension of that naming strategy. Should mconcat stay as is, or can anyone think of a better name?

I don‘t like the m in mappend and mconcat because it’s not very descriptive. Sometimes it means “monoid” as in mappend and mconcat, and sometimes it means “monad” as in mzero, mplus, and msum. So it might be a good idea to change mconcat to concat, at least in the long run. The current concat is just mconcat of the [a] instance of Monoid, so it could just be removed then.

comment:27 Changed 9 years ago by igloo

Resolution: invalid
Status: newclosed

Proposal tickets are no longer needed as part of the library submissions process. Instead, a normal ticket should be created once consensus has been achieved. Please see the process description for details.

comment:28 Changed 8 years ago by YitzGale

Cc: gale@… added

Since there is already so much discussion about the issue in this ticket, I am adding this comment here:

This is an old proposal. I was in favor of it back in the day, but now I am opposed, due to the existence of the semigroups package:

http://hackage.haskell.org/package/semigroups

This operator is already exported from Data.Semigroup. Library authors who have Monoid instances should also provide Semigroup instances. That makes the <> operator available.

Exporting <> from Data.Monoid would break any code that uses the synonymous operator from Data.Semigroup. And anyway, it's incorrect - this is really a semigroup operator, Monoid only adds the identity element.

Really Monoid should be a subclass of Semigroup, but that would be difficult to do at this stage. It is similar to the situation with Functor and Monad, unfortunately.

comment:29 Changed 8 years ago by tibbe

Resolution: invalid
Status: closednew

As per http://thread.gmane.org/gmane.comp.lang.haskell.libraries/11450 we now have consensus. Please apply the attached patch.

Changed 8 years ago by tibbe

comment:30 Changed 8 years ago by tibbe

Status: newpatch
Type: proposalfeature request

comment:31 Changed 8 years ago by tibbe

Version: 6.10.37.2.1

comment:32 Changed 8 years ago by duncan

Version: 7.2.17.3

I think we're ok as per the new library process:

  • We have had the discussion on the libraires list (the discussion took place before the new libs process was in place, but what happened does follow what the new library process calls for).
  • We've created a ticket on the trac (re-opened this ticket) with the patch and the link to the discussion

So now we just need the maintainer to apply it. That's GHC HQ, meaning Simon, Simon or Ian. If you're busy and want to delegate, I'm happy to take the blame myself! :-)

We've all been procrastinating on this for years. Can we please get this into base for the 7.4 release please? :-)

comment:33 in reply to:  28 Changed 8 years ago by duncan

Replying to YitzGale:

This is an old proposal. I was in favor of it back in the day, but now I am opposed, due to the existence of the semigroups package:

For the record, I disagree. Having a semigroup class is fine, but as long as it is outside of base then we still need <> in base otherwise in practice all the other packages are just not going to provide semigroup instances. Later on, if we move semigroup into base, or move monoid out of base into a different core package then that'll all be nice, we can move <> into semigroup etc. But lets not hold back everything just because it's not perfect. Whatever solution we use to fix Applicative between Functor and Monad, we'll be able to use the same solution to fix Semigroup.

comment:34 Changed 8 years ago by igloo

This turned out to have some bad interactions with <> in the pretty package; see this thread: http://www.haskell.org/pipermail/libraries/2011-November/017066.html

comment:35 Changed 8 years ago by tibbe

Resolution: fixed
Status: patchclosed

Fixed in ghc-7.4 branch.

ghc:

commit 7dfa17d4ed8fa7604cb681e375133db4773b8910
Author: Johan Tibell <johan.tibell@gmail.com>
Date:   Wed Jan 4 09:59:22 2012 -0800

    Be explicit about what we import from Data.Monoid

 compiler/ghci/RtClosureInspect.hs |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

base:

commit 556d174a555b993176e6766017b984e7a096a327
Author: Johan Tibell <johan.tibell@gmail.com>
Date:   Tue Aug 16 11:40:34 2011 +0200

    Add <> as an alias for mappend

 Data/Monoid.hs |    8 ++++++++
 1 files changed, 8 insertions(+), 0 deletions(-)

pretty:

commit f615a6aeb4abcdbfbfab67b0a82cef9a9a65ec49
Author: Johan Tibell <johan.tibell@gmail.com>
Date:   Wed Jan 4 10:06:50 2012 -0800

    Add note explaining why we use a different <>

 Text/PrettyPrint/HughesPJ.hs |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

We will have to separately sort out the pretty package needs to change if it wants to use the Data.Monoid.(<>) operator.

comment:36 Changed 8 years ago by igloo

Resolution: fixed
Status: closednew

comment:37 Changed 8 years ago by igloo

Owner: set to igloo

Not in HEAD

comment:38 Changed 8 years ago by igloo

If it won't generalise Text.Pretty.<>, then I wonder why we don't name it ++ instead (and remove the current ++ definition).

I guess the main argument against doing so would be error messages for beginners, but perhaps better error messages and/or helium are the answer to that.

comment:39 Changed 8 years ago by tibbe

It does generalize Text.Pretty.<>, but the interaction between <> and <+> in pretty is iffy with regards to fixity and associativity (and was that way before this addition, this simply brought it to light) and needs to be resolved.

comment:40 in reply to:  35 ; Changed 8 years ago by igloo

Oh, I missed this comment in your previous message:

We will have to separately sort out the pretty package needs to change if it wants to use the Data.Monoid.(<>) operator.

Surely having a single GHC release in which Data.Monoid and Text.PrettyPrint have conflicting definitions of <> is the worst case? Anyone importing both will need to change their code to disambiguate in order to work around a transient problem.

Why not just make both changes at once, with GHC 7.6.1? This could be done in the HEAD now, and should be released well under a year after 7.4.1.

comment:41 in reply to:  40 Changed 8 years ago by bos

Replying to igloo:

Why not just make both changes at once, with GHC 7.6.1? This could be done in the HEAD now, and should be released well under a year after 7.4.1.

The proposal is already over 3 years old, and has been stalled by immense amounts of bikeshedding. It would be ludicrous to delay it by another year just to sort out the incompatibility between the two modules, when Text.PrettyPrint is not at all widely used and the fix in any location that is actually subject to the problem takes less than a minute.

comment:42 Changed 8 years ago by YitzGale

There is widespread support for this. I think it should be implemented immediately. (Even though I myself voted against it, because in my bikeshed <> is exported from Data.Semigroup and not from Data.Monoid.)

Text.PrettyPrint is a far lower priority. After this much delay, we should implement the change even if it ultimately reduces the quality of the solution we provide for Text.PrettyPrint.

comment:43 in reply to:  38 Changed 8 years ago by JulesBean

Go ahead. Text.Pretty is not particularly widely used and users of Text.Pretty will not find this hard to resolve in their own code should they choose to do so.

As a historical note:

Replying to igloo:

If it won't generalise Text.Pretty.<>, then I wonder why we don't name it ++ instead (and remove the current ++ definition).

<> was chosen because of the match to Data.Sequence.<> not Text.Pretty.<> according to my scanning of the bug / email thread / my memory (on a recommendation from Ross Patterson).

comment:44 Changed 8 years ago by igloo

Milestone: Not GHC7.4.1
Resolution: fixed
Status: newclosed
Note: See TracTickets for help on using tickets.