OCaml中变体标签的模式匹配

时间:2015-02-14 23:48:25

标签: design-patterns pattern-matching ocaml variant

我有以下OCaml替换功能。

let rec subst x a f =                                                                                                                                    
    match f with                                                                                                                                           
    | Var s -> if s = x then a else Var s                                                                                                                 
    | Implies (f1, f2) -> Implies (subst x a f1, subst x a f2)                                                                                            
    | And (f1, f2) -> And (subst x a f1, subst x a f2)                                                                                                     
    | Or (f1, f2) -> Or (subst x a f1, subst x a f2)                                                                                                                                                                                   
    | True | False as e -> e

有些情况几乎相同,我想知道是否有办法以某种方式将它们分解。

理想情况下,我正在考虑形式的构造:

match f with
| tag (f1, f2) -> tag (subst x a f1, subst x a f2)
| ... 

将匹配我的所有二进制操作。

另一个用例是在to_string函数中,我们可以在其中:

match f with
| tag (f1, f2) -> print_string ((tag_to_string tag) ^ ...  )
| ... 

我知道这在OCaml中是不可能的,但是有一个模式或语言结构朝这个方向发展吗?

4 个答案:

答案 0 :(得分:3)

你不能,但你可以创建一个可以更容易使用的Binop构造函数。 所以你的类型定义会变成:

type binop = Implies | And | Or
type t =
  | Var of string
  | Binop of binop * t * t
  | True | False

然后你的功能可能是:

let rec subst x a f =
  match f with
  | Var s -> if s = x then a else f
  | Binop (b, f1, f2) -> Binop (b, subst x a f1, subst x a f2)
  | True | False -> f

请注意,这会使Binop使用比您的版本更多的单词,这对于较少的代码行来说是一个很好的代价。

答案 1 :(得分:1)

不,你不能绑定构造函数。如果您对此感到厌倦,可以编写映射器/文件夹类,这样您就不必重复自己了。

例如,在我的项目中,替换function看起来像这样(对于更复杂的语言)

let substitute x y = (object inherit mapper
  method! map_exp z = if Exp.(x = z) then y else z
end)#run

答案 2 :(得分:1)

(免责声明:我对Haskell比Ocaml更熟悉,所以我可能不会以最惯用的方式做事)

我不知道解决这个问题的一般方法(除了某种宏),但我学到并发现有用的一种编码方式如下:

type t =
  | Var of t
  | Implies of t * t
  | And of t * t
  | Or of t * t
  | True
  | False

let apply_t var implies and_t or_t true_t false_t = function
  | Var s -> var s
  | Implies (a, b) -> implies a b
  | And (a, b) -> and_t a b
  | Or (a, b) -> or_t a b
  | True -> true_t
  | False -> false_t

这应该有助于某些函数的定义和类型的抽象用法。在这种情况下,如果您必须定义更多这类函数,它只会导致更少的代码。不过,它可能会给你一些想法。通过更多帮助,我们可以看到如何轻松实现subst这样的函数

(* Ocaml constructors behave oddly *)
let impliest (a, b) = Implies (a, b)
let andt (a, b) = And (a, b)
let ort (a, b) = Or (a, b)

let rec subst x a =
  let varf s = if s = x then a else Var s in
  let bin_subst tag f1 f2 = tag (subst x a f1, subst x a f2) in
  apply_t varf (bin_subst impliest) (bin_subst andt) (bin_subst ort) True False

另一种可能更适合某些用例的可能性是将TrueFalse编码为单个参数,将该值作为apply_t函数中的参数。

答案 3 :(得分:1)

一种简单的方法是在匹配表达式上方编写嵌套函数,但您必须修改变体:

type t =
  | Var of t
  | Implies of (t * t) (* NOTE the parens around the arguments *)
  | And of (t * t)
  | Or of (t * t)
  | True
  | False

let rec subst x a f =
  let g f1 f2 = (subst x a f1, subst x a f2) in
  match f with
  | Var s -> if s = x then a else Var s
  | Implies (f1, f2) -> Implies (g f1 f2)
  | And (f1, f2) -> And (g f1 f2)
  | Or (f1, f2) -> Or (g f1 f2)
  | True | False as e -> e

有一些代码重复,因此它不像PatJ那样扩展,但它在某种意义上也更清晰,因为你只有三个二进制操作。

关于标记的想法,我不相信有一种方法可以根据参数的数量来绑定构造函数。有关详细信息,请参阅http://caml.inria.fr/pub/docs/manual-ocaml-400/patterns.html