Caching 在OCaml中扩展不可变类型(或:不可变类型的快速缓存)
我在ocaml中有一个递归的不可变数据结构,可以简化为如下内容:Caching 在OCaml中扩展不可变类型(或:不可变类型的快速缓存),caching,hashtable,ocaml,immutability,Caching,Hashtable,Ocaml,Immutability,我在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,有时它会变得非常复杂(非常深) 有一个对表达式求值的递归函数。
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
现在假设我正在将一个表达式映射到另一个表达式,我需要不断地检查一个表达式的结果,有时对同一个表达式检查多次,有时对最近使用该模式映射的表达式检查多次
{ someExpr with eexpr = TSum(someExpr, otherExpr) }
现在,result函数非常轻量级,但是多次运行它进行深度AST将不会得到很好的优化。我知道我可以使用Hashtbl缓存该值,但Hashtbl只能实现结构相等,因此无论如何它都需要遍历我的long-AST。
我知道最好的选择是在expr类型中包含一个可能不可变的“result”字段。但我不能
那么在Ocaml中有没有办法将一个值缓存到一个不可变的类型中,这样我就不必在每次需要时都急切地计算它了
谢谢 散列限制
expr\u expr
的值。通过这样做,程序中的结构相等值将共享完全相同的内存表示,并且可以用物理相等(=
)替换结构相等(=
)
这将使您快速开始在OCaml中考虑散列 可以使用函数接口控制哈希表使用的相等类型。我相信(=)的语义对于您的目的是合法的;i、 例如,如果A==B,那么对于任何纯函数f,fa=fb。因此,您可以缓存fa的结果。然后,如果您找到一个物理上等于A的B,那么缓存的值对于B是正确的 使用(==)进行散列的缺点是,散列函数会将所有结构上相等的对象发送到同一个散列桶,在那里它们将被视为不同的对象。如果表中有很多结构相同的对象,那么哈希就没有任何好处。该行为退化为线性搜索 您不能定义哈希函数来处理物理地址,因为垃圾收集器可以随时更改物理地址
但是,如果您知道您的表只包含相对较少的大ish值,那么使用物理相等可能适合您。我认为您可以合并上述两种想法:使用类似哈希的技术获得数据“纯表达式”部分的哈希,并将此散列用作
eval
函数的记忆表中的键
当然,这只适用于eval
函数确实只依赖于函数的“纯表达式”部分的情况,如您给出的示例所示。我认为这是一个相对普遍的情况,至少如果您限制自己存储成功的评估(例如,不会返回包含一些位置信息的错误)
编辑:一个小的概念证明:
type 'a _expr =
| Int of int
| Add of 'a * 'a
(* a constructor to avoid needing -rectypes *)
type pure_expr = Pure of pure_expr _expr
type loc = int
type loc_expr = {
loc : loc;
expr : loc_expr _expr;
pure : pure_expr (* or any hash_consing of it for efficiency *)
}
(* this is where you could hash-cons *)
let pure x = Pure x
let int loc n =
{ loc; expr = Int n; pure = pure (Int n) }
let add loc a b =
{ loc; expr = Add (a, b); pure = pure (Add(a.pure, b.pure)) }
let eval =
let cache = Hashtbl.create 251 in
let rec eval term =
(* for debug and checking memoization *)
Printf.printf "log: %d\n" term.loc;
try Hashtbl.find cache term.pure with Not_found ->
let result =
match term.expr with
| Int n -> n
| Add(a, b) -> eval a + eval b in
Hashtbl.add cache term.pure result;
result
in eval
let test = add 3 (int 1 1) (int 2 2)
# eval test;;
log: 3
log: 2
log: 1
- : int = 3
# eval test;;
log: 3
- : int = 3
哈希是一个伟大的功能!我不知道它的存在!但是我的AST有一些非常复杂的值,比如我提到的“a_complex_type”。它有惰性的值、函数,通常无法与复杂类型的结构相等进行比较。散列cons在这种情况下有效吗?在我的AST中很可能找不到相同的值(它也包含位置位置),但是当我使用构造{expr with eexpr=TAdd(expr,otherExpr)}时,我觉得has cons在那里是不必要的。但这是一篇伟大的、内容丰富的论文。谢谢现在,我已经读到ocaml物理相等在不可变结构上有未定义的行为。像@Jeffrey建议的那样,在没有犯罪的情况下使用它安全吗?这应该足够了!考虑到你的打字比这更复杂。如果它的所有成分都可以用hash consed构造函数构造,那么这就不会是问题。职位信息实际上是有问题的。如果没有这一点,您可以使用弱哈希表轻松地记忆
result
函数,也可以用于递归调用的中间结果。是的,我希望我可以更改结构以使用hash cons!但是非常感谢你的建议!谢谢Jeffrey的回复!因此,我很可能可以对一个列表和一个函数执行相同的操作,该函数将使用==查找列表,然后呢?我在ocaml手册中读到,物理等式对于不可变结构具有未定义的行为,尽管可以保证当A==B时,A=B(当然)。当我使用模式{expr with eexpr=TAdd(expr,otherExpr)}时,是否可以保证在TAdd(thisExpr,u)thisExpr==expr)中,哈希表比列表工作得更好,因为它将按照近似的结构等式(即,通常的哈希函数)排序到容器中。除非所有值在结构上都相等,否则这比只使用一个列表要好。(你可以定制你的散列函数来查看最常不同的部分。)就像我说的,我认为(=)对于不可变值的语义适合你的目的。对于什么是(==)到什么,没有强有力的保证,运行时可以任意使用纯值(FP如此酷的一个原因)。但我会说是的,在实践中。哦,我明白了!但是它必须在每次散列比较时遍历大AST,不是吗?我的“result”函数相当轻量级,所以它可能比急切地调用它要慢,不是吗?我正在考虑使用Hashtbl的位置,然后线性搜索。这对我来说是最好的,但遗憾的是,这是我正在处理的应用程序的具体设计。哈希函数不必遍历整个结构。这只是一个近似值。事实上,预定义的哈希函数不会遍历整个结构。您可以编写一个哈希函数,它可以执行任何您喜欢的操作(通过