代数数据类型的模式匹配

时间:2017-03-02 12:25:43

标签: pattern-matching ocaml algebraic-data-types

这是一个悬而未决的问题,但我从来没有找到一个让我满意的解决方案。

假设我有这种代数数据类型:

type t = A of int | B of float | C of string

现在,假设我想写一个compare函数 - 因为我想将我的值放在Map中,例如 - 我会这样写:

let compare t1 t2 =
  match t1, t2 with
    | A x, A y -> compare x y
    | A _, _ -> -1
    | _, A _ -> 1
    | B x, B y -> compare x y
    | B _, _ -> -1
    | _, B _ -> 1
    | C x, C y (* or _ *) -> compare x y

或者我可以这样写:

let compare t1 t2 = 
  match t1, t2 with
    | A x, A y -> compare x y
    | B y, B x -> compare x y
    | C x, C y -> compare x y
    | A _, _
    | B _, C _ -> -1
    | _ -> 1

如果我没错,说n是构造函数的数量,第一个compare将有3 * (n - 1) + 1个案例,第二个将有n + ((n - 2) * (n - 1)) / 2 + 2个案例

这是非常不满意的,因为:

  • n = 3(我们的案例):7或6个案例
  • n = 4:10或8个案例
  • n = 5:13或13个案例

它增长得非常快。

所以,我想知道,你这样做,或者你使用其他方法吗?

或者,更好的是,是否有可能做类似

的事情
let compare t1 t2 =
  match t1, t2 with
    | c1 x, c2 y -> 
      let c = compare c1 c2 in
      if c = 0 then compare x y else c

或者,

let compare (c1 x) (c2 y) = 
  let c = compare c1 c2 in
  if c = 0 then compare x y else c

编辑:如果两个构造函数对于señorDrupup(from Guadalup ;-P)是相等的,则添加一个比较

3 个答案:

答案 0 :(得分:6)

您可以使用ppx_deriving生成此功能。

以下将创建一个正确的函数compare : t -> t -> int

type t = A of int | B of float | C of string [@@deriving ord]

如果您感到好奇,或者无法使用ppx_deriving,这里是生成的代码,它使用与Reimer解决方案类似的策略。

% utop -require ppx_deriving.std -dsource
utop # type t = A of int | B of float | C of string [@@deriving ord];;
type t = | A of int | B of float | C of string [@@deriving ord]
let rec (compare : t -> t -> Ppx_deriving_runtime.int) =
  ((let open! Ppx_deriving_runtime in
      fun lhs  ->
        fun rhs  ->
          match (lhs, rhs) with
          | (A lhs0,A rhs0) -> Pervasives.compare lhs0 rhs0
          | (B lhs0,B rhs0) -> Pervasives.compare lhs0 rhs0
          | (C lhs0,C rhs0) -> Pervasives.compare lhs0 rhs0
          | _ ->
              let to_int = function
              | A _ -> 0
              | B _ -> 1
              | C _ -> 2
              in
              Pervasives.compare (to_int lhs) (to_int rhs))
  [@ocaml.warning "-A"]) ;;
type t = A of int | B of float | C of string
val compare : t -> t -> int = <fun>

答案 1 :(得分:4)

有几种选择。首先,您可以使用Obj模块直接比​​较表示(尽管这显然取决于实现):

type t = A of int | B of float | C of string

let compare_t a b =
  match (a, b) with
  | A x, A y -> compare x y
  | B x, B y -> compare x y
  | C x, C y -> compare x y
  | _ -> compare (Obj.tag (Obj.repr a)) (Obj.tag (Obj.repr b))

如果您希望它更具可移植性,您可以编写(或生成)一个为您提供数字标记的函数。事实证明,当前的OCaml编译器似乎正在寻找它并且似乎能够忽略底层函数调用。

let tag_of_t = function
  | A _ -> 0
  | B _ -> 1
  | C _ -> 2

let compare_t a b =
  match (a, b) with
  | A x, A y -> compare x y
  | B x, B y -> compare x y
  | C x, C y -> compare x y
  | _ -> compare (tag_of_t a) (tag_of_t b)

你仍然需要处理线性增长,而不是二次增长。

答案 2 :(得分:1)

如果您只需要比较使用Map.Make仿函数,即您不关心特定订单,则可以使用内置比较。特别是它适用于您的示例中给出的类型:

let compare t1 t2 = compare t1 t2

如果某些情况(但并非所有情况都超出了内置比较的范围)(例如它们是函数类型),您仍然可以将剩余的情况视为O(1)代码大小。例如:

type t = A of int -> int | B of float | C of string

let compare t1 t2 = match t1,t2 with
| A f1, A f2 -> ...
| A _, _ -> -1
| _, A _ -> 1
| _, _ -> compare t1 t2

如果一切都失败了,仍然可以选择进行元编程(例如通过camlp5)来自动创建一个长时间的比较。我过去已经这样做了,因为否则就是这样的顺序。

type three = Zero | One | Two

未指定(Pervasives.compare仅被指定为某些总订单)并且我想要自然顺序。