showsPrec and operator precedences

Since showsPrec hasn't any way to obtain the associativity of the context, I don't think it's possible to fix this as in, get exactly the minimal Haskell parenthesation back. To ensure correctness without adding more redundant parens than necessary, use >= in the showParen condition:

  showsPrec p e0 =
    case e0 of
     Const n -> shows n
     x :+: y -> showParen (p >= 6) $ (showsPrec 6 x) . (" :+: " ++) . (showsPrec 6 y)
     x :-: y -> showParen (p >= 6) $ (showsPrec 6 x) . (" :-: " ++) . (showsPrec 6 y)
     x :*: y -> showParen (p >= 7) $ (showsPrec 7 x) . (" :*: " ++) . (showsPrec 7 y)
     x :/: y -> showParen (p >= 7) $ (showsPrec 7 x) . (" :/: " ++) . (showsPrec 7 y)

This then yields

*Main> Const 1 :+: Const 2 :*: Const 3 :+: Const 4
(1 :+: 2 :*: 3) :+: 4
*Main> (Const 1 :+: Const 2) :*: (Const 3 :+: Const 4)
(1 :+: 2) :*: (3 :+: 4)
*Main> Const 1 :+: Const 2 :-: Const 3 :-: Const 4
((1 :+: 2) :-: 3) :-: 4
*Main> Const 1 :+: Const 2 :-: (Const 3 :-: Const 4)
(1 :+: 2) :-: (3 :-: 4)

Which doesn't look quite as nice as it could, but not too bad and certainly not wrong like the showParen (p > n) version. Basically, this gives what would be the minimal parenthesization if we had only infix, no infixl or infixr.

If you want only those parens to appear which are really necessary, you'll need to propagate more information than just an Int for context fixity. I implemented that kind of thing in my symbolic-math extension idea for HaTeX; essentially it just mirrors Haskell's infixl etc. annotations at runtime. For example,

     exaDisp $ 5 - (4 - 3) + 2 + 1

is then rendered like

Minimal-parenthesization in HaTeXMath


The following Show instance will print the Expr type with minimal parentheses:

data Expr =
  Const Int |
  Expr :+: Expr |
  Expr :-: Expr |
  Expr :*: Expr |
  Expr :/: Expr

infixl 6 :+:
infixl 6 :-:
infixl 7 :*:
infixl 7 :/:

instance Show Expr where
  showsPrec p e0 =
    case e0 of
     Const n -> showParen (p > 10) $ showString "Const " . showsPrec 11 n
     x :+: y -> showParen (p > 6) $ showsPrec 6 x . showString " :+: " . showsPrec 7 y
     x :-: y -> showParen (p > 6) $ showsPrec 6 x . showString " :-: " . showsPrec 7 y
     x :*: y -> showParen (p > 7) $ showsPrec 7 x . showString " :*: " . showsPrec 8 y
     x :/: y -> showParen (p > 7) $ showsPrec 7 x . showString " :/: " . showsPrec 8 y

This results in:

*Main> Const 1 :+: Const 2 :*: Const 3 :+: Const 4
Const 1 :+: Const 2 :*: Const 3 :+: Const 4
*Main> (Const 1 :+: Const 2) :*: (Const 3 :+: Const 4)
(Const 1 :+: Const 2) :*: (Const 3 :+: Const 4)
*Main> Const 1 :+: Const 2 :-: Const 3 :-: Const 4
Const 1 :+: Const 2 :-: Const 3 :-: Const 4
*Main> Const 1 :+: Const 2 :-: (Const 3 :-: Const 4)
Const 1 :+: Const 2 :-: (Const 3 :-: Const 4)

The general rule is

  • infix n: use showParen (p > n), showsPrec (n+1) on the left, and showsPrec (n+1) on the right
  • infixl n: use showParen (p > n), showsPrec n on the left, and showsPrec (n+1) on the right
  • infixr n: use showParen (p > n), showsPrec (n+1) on the left, and showsPrec n on the right
  • non-infix: use showParen (p > 10) and showsPrec 11 on the arguments

Following this rule will always yield correct syntax with minimal parentheses, except in one corner case: It can produce ambiguous output if you have infixl and infixr constructors with the same precedence level. As long as you don't do that, you should be fine.


How did I know what arguments to use with showParen? It's to match what Haskell does for derived Show instances. We can test those like this:

data T = P :# P | T P
  deriving Show

infix 6 :#

data P = P

instance Show P where
  showsPrec p P = shows p

We can see that with infix 6 :#, the Show T instance calls showsPrec 7 on the arguments to :#, and also it shows parentheses only at precedences > 6:

*Main> showsPrec 6 (P :# P) ""
"7 :# 7"
*Main> showsPrec 7 (P :# P) ""
"(7 :# 7)"

And for the ordinary constructor T, the generated instance calls showsPrec 11 on the argument and shows parens at precedences > 10:

*Main> showsPrec 10 (T P) ""
"T 11"
*Main> showsPrec 11 (T P) ""
"(T 11)"