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
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
: useshowParen (p > n)
,showsPrec (n+1)
on the left, andshowsPrec (n+1)
on the rightinfixl n
: useshowParen (p > n)
,showsPrec n
on the left, andshowsPrec (n+1)
on the rightinfixr n
: useshowParen (p > n)
,showsPrec (n+1)
on the left, andshowsPrec n
on the right- non-infix: use
showParen (p > 10)
andshowsPrec 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)"