假设我有一些类型支持多个二元运算符,每个运算符具有不同的优先级和关联性。如何编写正确包含子表达式的Show
实例?
我知道我在这里很密集,但我每次都错了 我试着去做。必须有一些机械程序,你可以遵循正确的工作,但我找不到它。有人可以告诉我一个例子吗?
我知道这最终归结为将所有内容包装在showParen
中,并使用showsPrec
显示具有正确幻数的子表达式,我可以使其几乎工作,但它在所有情况下都无法正常工作。
修改:请考虑以下代码
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 -> 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)
这个几乎正常工作:
*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
所以看起来优先级是正常的,但是关联性是不可能的。
答案 0 :(得分:14)
由于showsPrec
没有任何方法可以获得上下文的关联性,我认为不可能将其作为修复,完全获得最小的Haskell括号。为了确保正确性而不添加超出必要条件的冗余parens,请在>=
条件中使用showParen
:
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)
然后产生
*主> Const 1:+:Const 2:*:Const 3:+:Const 4
(1:+:2:*:3):+:4
*主> (Const 1:+:Const 2):* :( Const 3:+:Const 4)
(1:+:2):* :( 3:+:4)
*主> Const 1:+:Const 2: - :Const 3: - :Const 4
((1:+:2): - :3): - :4
*主> Const 1:+:Const 2: - :( Const 3: - :Const 4)
(1:+:2): - :(3: - :4)
这看起来并不尽如人意,但并不是太糟糕,而且肯定没错,就像showParen (p > n)
版本一样。基本上,如果我们只有infix
,没有infixl
或infixr
,这会给出最小的括号。
如果您只想要那些真正需要的parens,那么您需要传播更多信息,而不仅仅是Int
用于上下文修复。我实施了那种事in my symbolic-math extension idea for HaTeX;本质上它只是在运行时镜像Haskell的infixl
等注释。例如,
exaDisp $ 5 - (4 - 3) + 2 + 1
然后呈现为
答案 1 :(得分:7)
以下Show
实例将使用最少的括号打印Expr
类型:
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
这导致:
*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)
一般规则是
infix n
:左侧使用showParen (p > n)
,showsPrec (n+1)
,右侧使用showsPrec (n+1)
infixl n
:左侧使用showParen (p > n)
,showsPrec n
,右侧使用showsPrec (n+1)
infixr n
:左侧使用showParen (p > n)
,showsPrec (n+1)
,右侧使用showsPrec n
showParen (p > 10)
和showsPrec 11
遵循此规则将始终产生具有最小括号的正确语法,但在一个极端情况除外:如果infixl
和infixr
构造函数具有相同的优先级,则它可能产生不明确的输出。只要你不这样做,你应该没事。
我如何知道showParen
使用哪些参数?它匹配Haskell对派生Show
实例的作用。我们可以测试这样的:
data T = P :# P | T P
deriving Show
infix 6 :#
data P = P
instance Show P where
showsPrec p P = shows p
我们可以看到,对于infix 6 :#
,Show T
实例会在showsPrec 7
的参数上调用:#
,并且仅在优先级>处显示括号。 6:
*Main> showsPrec 6 (P :# P) ""
"7 :# 7"
*Main> showsPrec 7 (P :# P) ""
"(7 :# 7)"
对于普通构造函数T
,生成的实例在参数上调用showsPrec 11
并在优先级>处显示parens。 10:
*Main> showsPrec 10 (T P) ""
"T 11"
*Main> showsPrec 11 (T P) ""
"(T 11)"
答案 2 :(得分:0)
这个怎么样:
prec :: Expr -> Int
prec (Const _) = 10
prec (_ :*: _) = 7
prec (_ :/: _) = 7
prec (_ :+: _) = 6
prec (_ :-: _) = 6
instance Show Expr where
showsPrec p e0 =
case e0 of
Const n -> shows n
x :+: y -> showbin 6 " + " x y
x :-: y -> showbin 6 " - " x y
x :*: y -> showbin 7 " * " x y
x :/: y -> showbin 7 " / " x y
where showbin pr s x y =
showParen (p > pr) $
showsPrec pr x . (s ++) .
showParen (prec y == pr) (showsPrec pr y)
导致
*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 :-: Const 5)
1 + 2 - (3 - 4 - 5)
*Main> Const 1 :+: Const 2 :-: (Const 3 :-: (Const 4 :-: Const 5))
1 + 2 - (3 - (4 - 5))
*Main> Const 1 :+: Const 2 :-: (Const 3 :*: (Const 4 :/: Const 5))
1 + 2 - 3 * (4 / 5)
*Main> (Const 1 :*: (Const 2 :-: (Const 3 :*: (Const 4 :/: Const 5))))
1 * (2 - 3 * (4 / 5))