我在ocaml中有一个递归的不可变数据结构,可以简化为这样的结构:
type expr =
{
eexpr : expr_expr;
some_other_complex_field : a_complex_type;
}
and expr_expr =
| TInt of int
| TSum of (expr * expr)
| TMul of (expr * expr)
这是一个AST,有时会变得非常复杂(非常深)。
有一个递归函数来计算表达式。例如,让我们说,
let rec result expr =
match expr.eexpr with
| TInt i -> i
| TSum (e1, e2) -> result e1 + result e2
| TMul (e1, e2) -> result e1 * result e2
现在假设我将表达式映射到另一个表达式,我需要不断检查expr的结果,有时不止一次检查同一个expr,有时候对于最近使用模式映射的表达式
{ someExpr with eexpr = TSum(someExpr, otherExpr) }
现在,结果函数非常轻量级,但是对于深度AST运行很多次都不会非常优化。我知道我可以使用Hashtbl缓存值,但AFAIK Hashtbl只会进行结构相等,所以无论如何它都需要遍历我的长AST。 我知道最好的选择是在expr类型中包含一个可能不可变的“result”字段。但我不能。
所以在Ocaml中有什么方法可以将值缓存到不可变类型中,所以每次需要它时都不必急切地计算它吗?
谢谢!
答案 0 :(得分:5)
哈希 - 包含expr_expr
的值。通过这样做,程序中的结构相等的值将共享完全相同的内存表示,您可以通过物理相等(=
)替换结构相等(==
)。
这个paper可以让你快速开始在OCaml中使用hash-consing。
答案 1 :(得分:4)
您可以使用functorial接口来控制哈希表使用的相等类型。我相信(==)的语义对你的目的是合法的;即,如果A == B,则对于任何纯函数f,f A = f B.因此,您可以缓存f A的结果。然后,如果您发现物理上等于A的B,则缓存的值对于B是正确的。
使用(==)散列的缺点是散列函数会将所有结构相同的对象发送到同一个散列桶,在那里它们将被视为不同的对象。如果表中有很多结构相同的对象,则无法从散列中获益。行为退化为线性搜索。
您无法定义散列函数以使用物理地址,因为垃圾收集器可以随时更改物理地址。
但是,如果你知道你的表只包含相对较少的大值,那么使用物理相等可能对你有用。
答案 2 :(得分:1)
我认为你可以合并上面的两个想法:使用类似hash-consing的技术获取数据的“纯表达式”部分的哈希值,并使用此哈希值作为{{1的memoization表中的键功能。
当然,只有当你的eval
函数确实只依赖于函数的“纯表达式”部分时,这才有效,就像你给出的例子一样。我认为这是一个相对普遍的情况,至少如果你限制自己存储成功的评估(例如,不会返回包含一些位置信息的错误)。
编辑:一个小概念证明:
eval