Functional programming 模式匹配返回数学表达式的字符串表示形式

Functional programming 模式匹配返回数学表达式的字符串表示形式,functional-programming,ocaml,Functional Programming,Ocaml,我必须写一个函数转储,它接受一个表达式 type expression = | Int of int | Float of float | Add of expression * expression | Sub of expression * expression | Mult of expression * expression | Div of expression * expression ;; 并返回它的字符串表示形式。 例如: dump (Add (Int 1, Int 2));

我必须写一个函数转储,它接受一个表达式

type expression = 
| Int of int
| Float of float
| Add of expression * expression
| Sub of expression * expression
| Mult of expression * expression
| Div of expression * expression
;;
并返回它的字符串表示形式。 例如:

dump (Add (Int 1, Int 2));;
dump (Mult (Int 5, Add(Int 2, Int 3)), Int 1)
应分别返回

- : string = "1+2"
- : string = "5*(2+3)-1"
我写过这样的东西:

let rec dump e = match e with
    | Int a -> string_of_int a
    | Float a -> string_of_float a
    | Add (e1,e2) -> "(" ^ (dump e1) ^ "+" ^ (dump e2) ^ ")"
    | Sub (e1,e2) -> "(" ^ (dump e1) ^ "-" ^ (dump e2) ^ ")"
    | Mult (e1,e2) -> (dump e1) ^ "*" ^ (dump e2)
    | Div (e1,e2) -> (dump e1) ^ "/" ^ (dump e2)
;;
返回的表达式是正确的,但仍然不是最优的。 (对于Add(int1,int2))它是(1+2),应该是1+2)。我怎样才能解决这个问题?
(如果没有嵌套模式匹配,这不是一个好主意)

让我们考虑一下何时需要参数:

首先,总是围绕某些操作使用paren是错误的方法。一个术语是否需要插入括号不仅取决于该术语中使用的运算符,还取决于该术语是要插入的操作数

例如,当
1+2
3+4
+
的操作数时,它应该是
1+2+3+4
-无参数。但是,如果操作员是
*
,则需要是
(1+2)*(3+4)

那么,对于哪些运算符组合,我们需要paren

+
的操作数不需要加括号。如果操作数是乘积或商,则它们具有更高的优先级,如果操作数是差分的,则不需要paren,因为
x+(y-z)=x+y-z

-
有点不同
*
/
仍然不需要括号,因为它们具有更高的优先级,但是
+
-
如果它们位于第二个操作数中,则不需要括号,因为
x+y-z=(x+y)-z
,但是
x-y+z!=x-(y+z)

对于Mult,如果是Add或Sub,则两个操作数都需要加括号,但如果是Mult或Div,则不需要加括号


使用Div时,如果第一个操作数是Add或Sub,则需要将其括起来,而第二个操作数始终需要括起来(当然,除非它是Int或Float)。

我觉得您想建立一组简化规则,可以应用这些规则来生成表达式的“美化”或最简化形式,基于操作顺序,例如交换性、结合性等。例如
(a+a)=>a+a
(a*b)+c=>a*b+c
等等。

一个相当简单但相当通用的答案(适用于数学表达式以外的其他语法):选择前导(如果你很挑剔,选择关联性)只在子项构造函数的优先级低于当前构造函数时添加括号


更准确地说:当您想要打印一个构造函数
C(x1,x2,x3..)
时,您可以查看每个
xi
的头构造函数(如果
x1
D(y1,y2..)
,它的头构造函数是
D
),比较
C
D
的优先级。如果
D
的优先级较低,则在
x2
的字符串表示形式周围添加括号。首先,定义运算符的优先级列表:

module Prio = struct
  let div = 4
  let mul = 3
  let sub = 2
  let add = 1
end
一个有用的构造是“如果此条件为真,则用括号括起来”:

最后,定义一个辅助打印函数,该函数带有一个“priority”参数,意思是“顺便说一句,您被包装在一个具有priority X的表达式中,因此相应地保护您的输出”:


用调用
dump
并剥离外部paren的
pretty\u dump
包装
dump
有什么问题吗?@delnan:这仍然会产生
“1+(2+(3+4))”
对于
添加(Int 1,Add(Int 2,Add(Int 3,Int 4))
,而我假设他想要
“1+2+3+4”
。如果你这样做,
子(Int 42,Add(Int 23,Int 13))
被打印为
42-23+13
,这是错误的。除非你给
Sub
的优先级低于
Add
,在这种情况下,你会得到比你想要的更多的参数。我不认为完美的打印问题的好解决方案需要是完美的,因为它正好最小化了括号。解决打印问题这个问题(我认为)完全等同于解决解析问题,这确实相当复杂。对于解析,我们有工具为我们解决这个问题,但漂亮的打印通常是手工编写的任务;或者至少我不知道“打印机生成器”对关联性、先行性和所有这些都有很好的支持。因此,如果在输出质量和代码简单性之间有很好的折衷,我认为近似解决方案是好的。我的解决方案实现起来非常简单(只是比偏执的括号解决方案稍微困难一些)最后,我想指出的是,结合性/先行性问题往往集中在数学表达式上。一般来说,结构化语法的隐式冲突解决程度较低,这使得此解决方案对于打印任意语法的一般问题非常有效rms,即使在数学表达式上似乎很弱。
let wrap_if c str = if c then "("^str^")" else str
let dump e = 
  let rec aux prio = function
    | Int a -> string_of_int a
    | Float a -> string_of_float a
    | Add (e1,e2) -> 
        wrap_if (prio > Prio.add) (aux Prio.add e1 ^ "+" ^ aux Prio.add e2)
    | Sub (e1,e2) -> 
        wrap_if (prio > Prio.add) (aux Prio.add e1 ^ "-" ^ aux Prio.sub e2)
    | Mult (e1,e2) -> 
        wrap_if (prio > Prio.mul) (aux Prio.mul e1 ^ "*" ^ aux Prio.mul e2)
    | Div (e1,e2) -> 
        wrap_if (prio > Prio.mul) (aux Prio.mul e1 ^ "/" ^ aux Prio.div e2)
  in aux Prio.add e
;;