为递归数据结构自动生成差异pp

Automatically Generate difference pp for recursive data structures

OUnit framework 有一个函数 assert_equal 可以(除其他外)接受参数 pp_diff 以更易读的方式格式化两个输入的差异。由于数据结构在现实世界的应用程序中变得相当大,这似乎非常有用。

然而,手动实现这一点似乎也很乏味(尤其是在开发过程中,数据结构可能经常更改)。所以想知道有没有办法(最好是基于ppx的)生成这样的函数?

如果 OCaml 中(还)没有这样的东西,那么在通常的嫌疑人(例如 Haskell、Lisp)中是否有任何相关的东西可以被移植甚至只是用于灵感(因为我没有甚至对如何开始这样的实施有丝毫的暗示)?换句话说:如何为相互递归的功能数据结构生成有意义的差异漂亮打印机?

将数据结构转换为人类可读形式(然后对该表示进行操作)的一种方法是使用 Core.Std s-expressions;它们基本上是在 LISP s-expressions 之后建模的,并且 Core.Std 具有将数据与 s-expressions 相互转换的功能(以及一个语法扩展来自动化大部分无聊的部分)。您可以在 Real World OCaml 的第 17 章中找到很好的概述。

对于您的应用程序来说最重要的是,已经有计算 s-expressions 之间差异的功能(您也可以 pretty-print 它们,然后对它们使用普通的文本差异)。此功能可在 Core_extended.Std.Sexp.Diff.

中找到

示例:

open Core.Std                          (* basic Sexp functionality. *)
module XSexp = Core_extended.Std.Sexp  (* for Sexp diffs.           *)

type example = {
  a: string;
  b: int;
  c: float;
} with sexp (* syntax extension *)

let v1 = { a = "foo"; b = 1; c = 3.14 }
let v2 = { a = "bar"; b = 2; c = 3.14 }

let print_sexp s = print_endline (Sexp.to_string_hum s)

let sexp_diff s1 s2 =
  match XSexp.Diff.of_sexps ~original:s1 ~updated:s2 with
    None -> ""
  | Some(diff) -> XSexp.Diff.to_string diff

let main () =
  let s1 = sexp_of_example v1 in
  let s2 = sexp_of_example v2 in
  print_endline "=== first sexp ===";
  print_sexp s1;
  print_endline "=== second sexp ===";
  print_sexp s2;
  print_endline "=== print_diff output ===";
  XSexp.print_diff ~oc:stdout ~original:s1 ~updated:s2 ();
  print_endline "=== sexp_diff output ===";
  print_endline (sexp_diff s1 s2)

let () = main ()

这里,print_diff 是一个预定义函数,用于将差异打印到通道,sexp_diff 是一个简单的自定义函数,使用 Sexp.Diff 到 return 差异作为一个字符串。使用 corebuild -pkg core_extended example.native(或 .byte,或使用带有必要参数的 ocamlbuild)构建程序后,运行 程序应产生以下输出:

=== first sexp ===
((a foo) (b 1) (c 3.14))
=== second sexp ===
((a bar) (b 2) (c 3.14))
=== print_diff output ===
 a
- foo
+ bar
 b
- 1
+ 2
=== sexp_diff output ===
 a
- foo
+ bar
 b
- 1
+ 2