【问题标题】:pattern matching of variant tag in OCamlOCaml 中变体标签的模式匹配
【发布时间】:2015-02-14 23:48:25
【问题描述】:

我有以下 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 中是不可能的,但是有没有朝着这个方向发展的模式或语言结构?

【问题讨论】:

  • 虽然没有在 Ocaml 中实现,但您可能想阅读that blog post 了解与您描述的类似的假设功能。

标签: design-patterns pattern-matching ocaml variant


【解决方案1】:

你不能,但你可以创建一个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 比您的版本多使用一个字,这(在大多数情况下)是为更少的代码行付出的好代价。

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      (免责声明:我对 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 函数中的参数。

      【讨论】:

      • 你所说的“奇怪”是 ML 语言中的 ctor 没有功能,它们只是特殊的文字。确实,一旦你开始考虑它们的类型,你就会意识到它们的功能本质,并觉得 ML 中似乎缺少一些东西,但是 ML 和 Haskell 中的函数无法显示,而 ctor 可以,所以你不会说函数是奇怪?
      • 我的观点是,ctors 位于代数和函数值之间的“连接点”,因此它们可以适应语言中的任一组,但它们总是很奇怪。我认为这几乎就是 OP 问题的本质。
      • 这很好地解释了他们的行为。谢谢你。在写这个答案时,实际上阻碍我的是,在写它之前,我认为构造函数将元组作为参数,但是,正如你所说,它们只是特殊的文字。所以这只是我对语言的某些部分不熟悉的情况。我实际上更倾向于使用 Ocaml 而不是 Haskell 来进行新项目,所以不要认为这是我在抨击 Ocaml。
      • 我不认为这是在抨击 ocaml:“奇怪”是指事情看起来不正常,而正常是(主观上)通过经验建立的概念。作为一名 OCamler,我想给你我的观点,他起初认为 Haskell 演员很奇怪 :)
      【解决方案4】:

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

      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

      【讨论】:

      • 我认为您的建议不正确。构造函数 Or 需要两个参数(而不是二进制元组值,例如 g 返回的那个)。
      • 谢谢尼莫!修好了。
      • 和“(t, t)”应该是“(t * t)”:)
      • 再次感谢。这是我最后一次在没有编译/测试的情况下发帖。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-05
      • 1970-01-01
      • 2022-01-11
      • 2011-05-31
      • 1970-01-01
      相关资源
      最近更新 更多