打印表达式时最小化括号

Minimize parenthesis when printing expression

我有一个简单的算术表达式数据结构,我希望能够打印它。为了简单起见,我在这里举了一个包含 3 个二进制运算、加法、乘法和除法的示例。定义如下所示:

module ExprPrint where

import Text.Printf

data Expr = Lit Int
          | Add Expr Expr
          | Mul Expr Expr
          | Div Expr Expr

instance Show Expr where
  show (Lit x) = show x
  show (Add e1 e2) = printf "(%s) + (%s)" (show e1) (show e2)
  show (Mul e1 e2) = printf "(%s) * (%s)" (show e1) (show e2)
  show (Div e1 e2) = printf "(%s) / (%s)" (show e1) (show e2)

我的目标是打印数据结构,同时删除所有多余的括号。当然,我上面实现的简单显示功能包含了太多的功能。所以我想做的是让 Show 实例优先(DivMul 超过 Add)和关联性(AddMul 是关联的,而 Div 是左关联的)考虑到操作。

这里有一些例子:

one = Lit 1

-- Shows "((1) + (1)) + (1)" but should be 1 + 1 + 1
addAssoc = show $ Add (Add one one) one
-- Shows "((1) * (1)) * (1)" but should be 1 * 1 * 1
mulAssoc = show $ Mul (Mul one one) one
-- Shows "((1) / (1)) / (1)" but should be 1 / 1 / 1
divAssoc = show $ Div (Div one one) one
-- Shows "(1) / ((1) / (1)) but should be 1 / (1 / 1)
divAssoc2 = show $ Div one (Div one one)

-- Show "((1) * (1)) + (1)" but should 1 * 1 + 1
addPrec = show $ Add (Mul one one) one
-- Show "(1) + ((1) * (1))" but should show 1 + (1 * 1)
addPrec2 = show $ Add one (Mul one one)

是否有一个 "easy" 可以在展示实例中考虑到这一点?我想我可以通过考虑所有情况来做到这一点,但这将是功能的爆炸式增长。是否有某种算法或已知方法来处理此问题?

希望大神指点!

谢谢。

根据 show 的实例不够强大,无法避免多余的括号,因为它没有任何可用的优先级信息。您需要根据 showsPrec 来编写您的实例,就像这样:

module ExprPrint where

import Text.Show

data Expr = Lit Int
          | Add Expr Expr
          | Mul Expr Expr
          | Div Expr Expr

instance Show Expr where
  showsPrec prec (Lit x) = showsPrec prec x
  showsPrec prec (Add e1 e2) = showParen (prec >= 7) $ showsPrec 7 e1 . showString " + " . showsPrec 7 e2
  showsPrec prec (Mul e1 e2) = showParen (prec >= 8) $ showsPrec 8 e1 . showString " * " . showsPrec 8 e2
  showsPrec prec (Div e1 e2) = showParen (prec >= 8) $ showsPrec 8 e1 . showString " / " . showsPrec 8 e2

我选择 6 和 7 作为你的优先级,因为这是 Haskell 用于它自己的 +*div 运算符的,但它应该是很明显你会如何选择不同的。

至于结合性,通常没有完美的方法来做到这一点,但您可以在您的案例中通过一些优先级调整来伪造它,因为数学没有任何具有不同结合性的相同优先级的运算符。这是一个如何做到这一点的例子(我添加了 Exp,优先级为 8,以给出右关联方式的例子):

module ExprPrint where

import Text.Show

data Expr = Lit Int
          | Add Expr Expr
          | Mul Expr Expr
          | Div Expr Expr
          | Exp Expr Expr

instance Show Expr where
  showsPrec prec (Lit x) = showsPrec prec x
  showsPrec prec (Add e1 e2) = showParen (prec >= 7) $ showsPrec 6 e1 . showString " + " . showsPrec 7 e2
  showsPrec prec (Mul e1 e2) = showParen (prec >= 8) $ showsPrec 7 e1 . showString " * " . showsPrec 8 e2
  showsPrec prec (Div e1 e2) = showParen (prec >= 8) $ showsPrec 7 e1 . showString " / " . showsPrec 8 e2
  showsPrec prec (Exp e1 e2) = showParen (prec >= 9) $ showsPrec 9 e1 . showString " ^ " . showsPrec 8 e2

仍然不完美,因为它仍然不知道 AddMul 的关联 属性,所以 Mul one (Mul one one) 将显示为 1 * (1 * 1) 而不是 1 * 1 * 1,但我认为没有任何可能的方法来解决这个问题,因为除法不共享 属性,但由于它与乘法具有相同的优先级,所以你不能在 showsPrec.

中区分它们

实际上,你可以偷看下一层并重新关联:

module ExprPrint where

import Text.Show

data Expr = Lit Int
          | Add Expr Expr
          | Mul Expr Expr
          | Div Expr Expr
          | Exp Expr Expr

instance Show Expr where
  showsPrec prec (Lit x) = showsPrec prec x
  showsPrec prec (Add e1 (Add e2 e3)) = showsPrec prec (Add (Add e1 e2) e3)
  showsPrec prec (Add e1 e2) = showParen (prec >= 7) $ showsPrec 6 e1 . showString " + " . showsPrec 7 e2
  showsPrec prec (Mul e1 (Mul e2 e3)) = showsPrec prec (Mul (Mul e1 e2) e3)
  showsPrec prec (Mul e1 e2) = showParen (prec >= 8) $ showsPrec 7 e1 . showString " * " . showsPrec 8 e2
  showsPrec prec (Div e1 e2) = showParen (prec >= 8) $ showsPrec 7 e1 . showString " / " . showsPrec 8 e2
  showsPrec prec (Exp e1 e2) = showParen (prec >= 9) $ showsPrec 9 e1 . showString " ^ " . showsPrec 8 e2

我认为这是完美的。您所有的测试用例现在都通过了。