Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
GHC
GHC
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 4,265
    • Issues 4,265
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
    • Iterations
  • Merge Requests 419
    • Merge Requests 419
  • Requirements
    • Requirements
    • List
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
  • Security & Compliance
    • Security & Compliance
    • Dependency List
    • License Compliance
  • Operations
    • Operations
    • Incidents
    • Environments
  • Analytics
    • Analytics
    • CI / CD
    • Code Review
    • Insights
    • Issue
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • Glasgow Haskell Compiler
  • GHCGHC
  • Wiki
    • Proposal
  • left assoc semigroup op

Last edited by Takenobu Tani Nov 06, 2020
Page history New page

left assoc semigroup op

Left-Associative Semigroup Operator Alias

Mailing list discussion in progress on on {libraries,ghc-devs}@haskell.org

related reddit discussion on /r/haskell

Problem

With the implementation of prime:Libraries/Proposals/SemigroupMonoid, Semigroup will become a superclass of Monoid, and consequently Semigroup((<>)) will be re-exported alongside Monoid from the Prelude module.

-- reduced/simplified definition
class Semigroup a where
    (<>) :: a -> a -> a

infixr 6 <>

The infixr 6-fixity for <> was already introduced 4 years ago, when we added Data.Monoid.<> as alias for mappend (which differs from infixr 5 ++). See also #3339 (closed) for some of the discussion that began in 2009 leading up to the final infixr 6 <> decision.

Conflicting fixities of <> in pretty printing APIs

However, there are a few popular pretty-printing modules which already define a <> top-level binding for their respective semigroup/monoid binary operation. The problem now is that those <> definitions use a different operator fixity/associativity:

-- pretty
module Text.PrettyPrint.Annotated.HughesPJ where

infixl 6 <>
infixl 6 <+>
infixl 5 $$, $+$

-- pretty
module Text.PrettyPrint.HughesPJ where

infixl 6 <>
infixl 6 <+>
infixl 5 $$, $+$
-- template-haskell
module Language.Haskell.TH.PprLib where

infixl 6 <> 
infixl 6 <+>
infixl 5 $$, $+$
-- ghc
module Outputable
infixl 9 <> 
infixl 9 <+>
infixl 9 $$, $+$

-- ghc
module Pretty where

infixl 6 <>
infixl 6 <+>
infixl 5 $$, $+$

On the other hand, the popular hackage:ansi-wl-pprint package does use right-associative operators:

module Text.PrettyPrint.ANSI.Leijen where

infixr 6 <>
infixr 6 <+>

Other pretty printers also using a infixr 6 <>, <+> definition:

  • hackage:annotated-wl-pprint
  • hackage:mainland-pretty

Changing <>'s associativity in pretty-printing APIs

Changing the fixity of pretty's <> would however results in a semantic change for code which relies on the relative fixity between <+> and <> as was pointed out by Duncan back in 2011 already:

So I was preparing to commit this change in base and validating ghc when I discovered a more subtle issue in the pretty package:

Consider

a <> empty <+> b

The fixity of <> and <+> is critical:

  (a <> empty) <+> b
= {- empty is unit of <> -}
  (a         ) <+> b

  a <> (empty <+> b)
= {- empty is unit of <+> -}
  a <> (          b)

Currently Text.Pretty declares infixl 5 <>, <+>. If we change them to be infixr then we get the latter meaning of a <> empty <+> b. Existing code relies on the former meaning and produces different output with the latter (e.g. ghc producing error messages like "instancefor" when it should have been "instance for").

Unsatisfying Situation Seeking a Long-term Solution

Consequently, it's confusing and bad practice to have a soon-to-be-in-Prelude <> operator whose meaning depends on which imports are currently in scope. Moreover, a module needs to combine pretty-printing monoids and other non-pretty-printing monoids, the conflicting <>s operator needs to be disambiguated via module qualification or similiar techniques.

However, there also seems to be a legitimate use-case for a left-associative <> operator.

Alternative Suggestions

David Terei suggests among other things to

Switch <> to infixr 67 and <+> to infixr 56, some code can still break, but arguably code relying on unintuitive semantics (since somewhat odd <> and <+> have same precedence when both treat empty as identity).

resulting in

infixr 7 <>
infixr 6 <+>
infixr 5 $$, $+$

Proposed Solution

Leave Semigroup((<>)) as infixr 6, and add a standardised left-associative alias for <> to the Data.Semigroup vocabulary, i.e.

module Data.Semigroup where

infixl 6 ><

-- | Left-associative alias for (right-associative) 'Semigroup' operation '(<>)'
(><) :: Semigroup a => a -> a -> a
(><) = (<>)

Bikesheds for ><

  • .<>
  • <~>
  • <#>
Clone repository

GHC Home
GHC User's Guide

Joining In

Newcomers info
Mailing Lists & IRC
The GHC Team

Documentation

GHC Status Info
Working conventions
Building Guide
Debugging
Commentary

Wiki

Title Index
Recent Changes