showsPrec和运算符优先级

时间:2014-12-14 17:26:17

标签: string haskell operator-precedence

之前我曾经问过这个问题,但似乎我的问题太过狭隘。那么让我们看看我是否可以解释我实际上在追求的内容。

假设我有一些类型支持多个二元运算符,每个运算符具有不同的优先级和关联性。如何编写正确包含子表达式的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

所以看起来优先级是正常的,但是关联性是不可能的。

3 个答案:

答案 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,没有infixlinfixr,这会给出最小的括号。

如果您只想要那些真正需要的parens,那么您需要传播更多信息,而不仅仅是Int用于上下文修复。我实施了那种事in my symbolic-math extension idea for HaTeX;本质上它只是在运行时镜像Haskell的infixl等注释。例如,

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

然后呈现为

Minimal-parenthesization in HaTeXMath

答案 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

遵循此规则将始终产生具有最小括号的正确语法,但在一个极端情况除外:如果infixlinfixr构造函数具有相同的优先级,则它可能产生不明确的输出。只要你不这样做,你应该没事。

我如何知道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))