通过功能组合统一OCaml模式

时间:2016-11-04 11:55:56

标签: pattern-matching ocaml unification

假设我已经手动定义了一些代码:

type test  = A of bool | B | C of bool * bool 
type test2 = D | E of bool * bool 
type test3 = F | G of bool | H of bool

let f = function | A(x) -> E(x,true) | B -> D | C(a,b) -> E(b,a)

let g = function  | D -> F | E(a,b) -> if a then G(b) else H(b)

现在我想将组合g gf评估为不需要使用中间表示test2的函数。我一直在考虑结构统一,但我提出以下问题:

  1. 在声明let h x = g (f x)
  2. 时,OCaml会自动执行此类统一吗?
  3. 如果没有,是否存在可以在OCaml中实现的通用算法,以便compile进入ml文件中,如此需要?
  4. 提前致谢

2 个答案:

答案 0 :(得分:3)

声明

我不是OCaml编译器开发人员。如果你想得到他们的意见,那么最好在邮件列表上联系他们。

第1部分

理论上,现代的OCaml(我试过4.0 {3,4} + flambda)能够消除test2类型的一些中间数据结构。但是,要实现此目的,您需要传递特殊优化选项,并将[@@inlined always]添加到函数f,否则即使使用疯狂的内联选项(例如-inline 10000-inline-toplevel 10000也不会内联它。

然而,在一般情况下,对于更大的功能,这可能不起作用。在这里,我想,你所展示的例子是一个玩具的例子,在现实生活中你面临着更大的功能和两个以上的成分(即数百个具有数百种成分的构造函数),否则,它是不值得优化这一点)。

第2部分

理论

说到一般算法,如果我们太挑剔,那么就模式匹配中的->权利而言,可能存在任何OCaml表达式,即图灵完备程序是不可能的。所以,我们有一个等同的决策问题。即使我们将限制表达语言,通过禁止循环和副作用,问题仍然是NP难,所以尝试解决它可能是不值得的。但是,如果我们将更多地限制自己,通过禁止除了具有普通操作数的构造函数应用程序之外的任何表达式,那么我们将实际编码有限状态机(FSM)。有很多明确定义的FSM优化和状态最小化算法,因此从中删除冗余并不困难。

实践

在实践中,我不太可能编写一个将ml代码转换为ml代码的函数。根据我的实际任务,我会考虑以下方法。

可输入的一组输入

如果居住类型test的值集实际上是有限的(即,如果它适合OCaml数组),那么我会尝试编写一个函数来枚举类型{{1}的所有可能值},并存储组合的结果。您可以使用test来计算[@@deriving enumerate]

类型的所有可能值
test

然后,您可以为类型# type test = A of bool | B | C of bool * bool [@@deriving enumerate];; type test = A of bool | B | C of bool * bool val all_of_test : test list = [A false; A true; B; C (false, false); C (true, false); C (false, true); C (true, true)] 的每个值分配序数,并进行O(1)转换以键入test。此外,根本没有分配,因为我们已事先预先计算了所有构造函数。

但是,您仍然需要为我们的工作方法转换test3,此操作将需要test -> int个分支,其中log(k)是类型{{1}中的构造函数的数量}。从big-O表示法的角度来看,k是不变的。但是如果它真的很大,那么你可以尝试使所有构造函数无论如何,例如,将test表示为两个构造函数kA of bool。在这种情况下,您将进行纯O(1)转换,没有开销(只是一个数组取消引用)。

具有未解释函数的可枚举输入

如果构造函数具有A_trueA_false类型的值,则实际上不可能枚举它们。但是,如果转换int没有查看此值,只是重新排列它们,即将它们视为Uninterpreted functions,那么您应该尝试使用GADT将此参数与输入语言分离。假设您有以下数据构造函数:

string

结果h对于所有| C of string 都是相同的。然后根本不需要将h (C s)传递给s。但是,您仍希望将有效负载s传递给其他函数,例如,传递给h。可以使用GADT,首先我们将有效负载与构造函数分离:

s

然后我们将能够将查询作为两个不同的参数传递给函数:

exec

一般情况

如果以上两种情况不适用于您,那么您需要编写自己的编译器:)看起来,您正在实现某种语言的解释器,它使用OCaml作为宿主语言,而您我希望OCaml能为你做优化。嗯,OCaml确实不是编写解释器的不错选择,但它不能优化你的DSL,因为它不知道你的DSL的语义。所有优化都是关于语义规则的巧妙应用。所以,这意味着,如果您对解释器的性能不满意,那么您需要编写自己的优化编译器。首先,您需要设计执行查询的抽象机器,然后编写一个针对此机器的优化器。

答案 1 :(得分:2)

对于旧版本的OCaml,这是不确定的。

但是,如果您激活flambda optimization,您可以确信您将从这种优化中获益。

请注意,在构建编译器时必须激活flambda(如果使用opam,则有专用开关)。你的编译时间可能会长一些,但这完全是值得的。

如果您想确保它内联(并因此简化),您可以在函数声明中使用[@@inline always]属性,或在函数调用中使用[@@inlined always]