showsPrec和运算符优先级

Mat*_*hid 22 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)
Run Code Online (Sandbox Code Playgroud)

几乎正常:

*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)
Run Code Online (Sandbox Code Playgroud)

但不完全:

*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
Run Code Online (Sandbox Code Playgroud)

因此看起来优先级是正常的,但关联性是不可靠的.

lef*_*out 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)
Run Code Online (Sandbox Code Playgroud)

然后产生

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

哪个看起来不是那么好,但不是太糟糕,当然不会像showParen (p > n)版本一样错.基本上,这给出了最小的括号,如果我们只有infix,没有infixlinfixr.

如果您只想要那些真正需要的parens,那么您需要传播更多信息而不仅仅是Int上下文固定.我在HaTeX的符号数学扩展思想中实现了那种东西; 本质上它只是infixl在运行时镜像Haskell 等注释.例如,

     exaDisp $ 5 - (4 - 3) + 2 + 1
Run Code Online (Sandbox Code Playgroud)

然后呈现像

HaTeXMath中的最小括号


Bri*_*man 10

以下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
Run Code Online (Sandbox Code Playgroud)

这导致:

*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)
Run Code Online (Sandbox Code Playgroud)

一般规则是

  • 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
Run Code Online (Sandbox Code Playgroud)

我们可以看到,infix 6 :#Show T实例调用showsPrec 7的参数:#,并且也只有在优先级> 6显示了括号:

*Main> showsPrec 6 (P :# P) ""
"7 :# 7"
*Main> showsPrec 7 (P :# P) ""
"(7 :# 7)"
Run Code Online (Sandbox Code Playgroud)

对于普通构造函数T,生成的实例调用showsPrec 11参数并显示优先级> 10的parens:

*Main> showsPrec 10 (T P) ""
"T 11"
*Main> showsPrec 11 (T P) ""
"(T 11)"
Run Code Online (Sandbox Code Playgroud)