免责声明
我不是 OCaml 编译器开发人员。如果您想获得他们的意见,那么最好在邮件列表中与他们联系。
第 1 部分
理论上,现代 OCaml(我已尝试使用 4.0{3,4}+flambda)能够消除test2 类型的一些中间数据结构。但是,要实现这一点,您需要传递特殊的优化选项,并将[@@inlined always] 添加到函数f,否则即使使用像-inline 10000 和-inline-toplevel 10000 这样的疯狂内联选项,它也不会内联。
但是,在一般情况下,对于更大的功能,这可能不起作用。在这里,我想,您展示的示例是一个玩具示例,在现实生活中,您面临着更大的功能和两个以上的组合(即数百个具有数百个组合的构造函数),否则,它根本不值得优化)。
第 2 部分
理论
说到一般算法,如果我们太挑剔的话,那是不可能的,因为模式匹配中->的右边可以有任何OCaml表达式,即图灵完备的程序。所以,我们有一个等价的决策问题。即使我们通过禁止循环和副作用来限制表达式语言,问题仍然是 NP-hard,因此尝试解决它可能不值得。但是,如果我们进一步限制自己,通过禁止除构造函数应用程序之外的任何表达式,那么我们实际上将编码一个有限状态机(FSM)。有很多定义明确的 FSM 优化和状态最小化算法,因此从中删除冗余并不难。
练习
实际上,我可能不会编写将ml 代码转换为ml 代码的函数。根据我的实际任务,我会考虑以下方法。
可枚举的输入集
如果test 类型的值集实际上是有限的(即,如果它适合 OCaml 数组),那么我会尝试编写一个函数来枚举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)]
然后,您可以为test 类型的每个值分配一个序数,并通过O(1) 转换为test3 类型。此外,根本不会有任何分配,因为我们已经预先计算了所有构造函数。
但是,您仍然需要转换 test -> int 才能使我们的方法工作,并且此操作将需要 log(k) 分支,其中 k 是 test 类型的构造函数的数量。从大 O 符号的角度来看,k 是恒定的。但如果它真的很大,那么你可以尝试让你的所有构造函数都没有参数,例如,将A of bool 表示为两个构造函数A_true 和A_false。在这种情况下,您将进行纯 O(1) 转换,没有开销(只是一个数组取消引用)。
具有未解释函数的可枚举输入
如果存在具有int 或string 类型值的构造函数,则几乎不可能枚举它们。但是,如果转换 h 不查看此值,而只是重新排列它们,即将它们视为 Uninterpreted functions,那么您应该尝试使用 GADT 将这些参数与输入语言分离。假设您有以下数据构造函数:
| C of string
结果h (C s) 对于所有s 都是相同的。然后根本不需要将s 传递给h。但是,您仍然希望将有效负载 s 传递给其他函数,例如 exec。可以使用 GADT,首先我们将有效负载与构造函数解耦:
| C : string query
然后我们将能够将查询作为两个不同的参数传递给函数:
val exec : 'a query -> 'a -> unit
一般情况
如果上述两种情况不适用于您,那么您需要编写自己的编译器 :) 看起来,您正在实现某种语言的解释器,它使用 OCaml 作为宿主语言,而您'希望 OCaml 会为你做优化。好吧,OCaml 确实是编写解释器的一个不错的选择,但它不能优化你的 DSL,因为它不知道你的 DSL 的语义。语义规则的巧妙应用是所有优化的目的。因此,这意味着,如果您对解释器的性能不满意,那么您需要编写自己的优化编译器。首先,您需要设计执行查询的抽象机器,然后编写一个针对该机器的优化器。