【问题标题】:showsPrec and operator precedencesshowPrec 和运算符优先级
【发布时间】:2014-12-14 17:26:17
【问题描述】:

我之前问过这个问题,但似乎我的问题措辞过于狭隘。所以让我们看看我能不能解释一下我到底在追求什么。

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

所以看起来 precedence 是可以的,但是 associativity 是borked。

【问题讨论】:

标签: string haskell operator-precedence


【解决方案1】:

以下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,并在优先级> 10处显示括号:

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

【讨论】:

    【解决方案2】:

    由于showsPrec 没有任何方法来获得上下文的关联性,我认为不可能像这样解决这个问题,得到最小的 Haskell 括号。为了确保正确性而不添加不必要的冗余括号,请在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)
    

    然后产生

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

    虽然看起来不太好,但也不算太糟糕,当然也不会像 showParen (p > n) 版本那样出错。基本上,如果我们只有infix,没有infixlinfixr,这将给出最小的括号。

    如果您只希望出现真正必要的括号,则需要传播更多信息,而不仅仅是 Int 以确保上下文固定。我实现了那种东西in my symbolic-math extension idea for HaTeX;本质上它只是在运行时反映 Haskell 的 infixl 等注释。例如,

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

    然后呈现为

    【讨论】:

      【解决方案3】:

      这个怎么样:

      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))
      

      【讨论】:

        猜你喜欢
        • 2011-06-21
        • 2020-03-05
        • 1970-01-01
        • 2021-03-29
        • 2013-07-31
        • 2023-03-07
        • 2011-07-07
        相关资源
        最近更新 更多