【问题标题】:Extending Immutable types (or: fast cache for immutable types) in OCaml在 OCaml 中扩展不可变类型(或:不可变类型的快速缓存)
【发布时间】:2012-02-03 01:20:07
【问题描述】:

我在 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 类型中包含一个可能不可变的“结果”字段。但我做不到。

那么在 Ocaml 中是否有任何方法可以将值缓存到不可变类型,这样我就不必每次需要时都急切地计算它?

谢谢!

【问题讨论】:

    标签: caching hashtable ocaml immutability


    【解决方案1】:

    Hash-cons expr_expr 的值。通过在您的程序中执行此结构相等的值将共享完全相同的内存表示,您可以用物理相等 (==) 替换结构相等 (=)。

    这个paper 应该让你快速开始在 OCaml 中使用哈希函数。

    【讨论】:

    • Hash-cons 是一个很棒的功能!我不知道它的存在!但是我的 AST 有一些非常复杂的值,比如我提到的“a_complex_type”。它有惰性值、函数,通常无法与 a_complex_type 的结构相等性进行比较。 hash-cons 在这种情况下会起作用吗?
    • 另外,在我的 AST(也包含位置位置)中找到相同的值很可能是不可能的,但是当我使用构造 { expr with eexpr =TAdd(expr, otherExpr) } 时,似乎对我来说,那里的缺点是不必要的。但这是一篇很棒且内容丰富的论文。谢谢!现在,我读到 ocaml 物理平等在不可变结构上具有未定义的行为。像@Jeffrey 提议的那样,在没有缺点的情况下使用它是否安全?这应该就够了!
    • 关于您键入的事实比这更复杂。如果它的所有组成部分都可以用散列构造函数来构造,那将不是问题。位置信息实际上是有问题的。没有它,你可以很容易地记住带有弱哈希表的 result 函数,也可以用于递归调用的中间结果。
    • 是的,我希望我可以更改结构以使用 hash-cons !不过真的很感谢你的建议!
    【解决方案2】:

    您可以使用函数接口来控制哈希表使用的相等类型。我相信 (==) 的语义对于您的目的是合法的;即,如果 A == B,则对于任何纯函数 f,f A = f B。所以你可以缓存f A的结果。然后如果你找到一个物理上等于A的B,那么缓存的值对于B是正确的。

    使用 (==) 进行散列的缺点是散列函数会将所有结构上相等的对象发送到同一个散列桶,在那里它们将被视为不同的对象。如果表中有很多结构上相同的对象,则散列不会给您带来任何好处。行为退化为线性搜索。

    您不能定义散列函数来处理物理地址,因为垃圾收集器可以随时更改物理地址。

    但是,如果您知道您的表将只包含相对较少的大值,则使用物理相等可能对您有用。

    【讨论】:

    • 感谢 Jeffrey 的出色回应!所以很可能我可以对 List 和一个使用 == then 查找列表的函数执行相同的行为?我在 ocaml 手册中读到,物理相等对于不可变结构具有未定义的行为,尽管可以保证当 A == B 时 A = B (当然)。当我使用模式 { expr with eexpr = TAdd(expr, otherExpr) } 时,是否可以保证在 TAdd(thisExpr, _) thisExpr == expr 中?
    • 哈希表比列表工作得更好,因为它会通过近似的结构相等性(即通常的哈希函数)分类到 bin 中。除非您的所有值在结构上都相同,否则这将比仅使用一个列表更好。 (您可以定制散列函数以查看最常见的不同部分。)就像我说的,我认为 (==) 用于不可变值的语义对于您的目的来说是可以的。对什么是 (==) 没有强有力的保证,运行时可以任意巧妙地使用纯值(FP 如此酷的原因之一)。但我会说是的,在实践中。
    • 哦,我明白了!但是,每次进行哈希比较时,它都必须遍历大 AST,不是吗?我的“结果”函数相当轻量级,所以结果可能会比急切地调用它要慢,不是吗?
    • 我正在考虑可能使用一个 Hashtbl 的位置,然后线性搜索。这对我来说是最好的,但很遗憾,我正在处理的应用程序的设计方式非常具体
    • 哈希函数不必遍历整个结构。这只是一个近似值。事实上,预定义的散列函数不会遍历整个结构。你可以编写一个散列函数来做任何你喜欢的事情(通过函数接口)。它只需要遵守明显的语义(通过相等函数比较相等的两个值必须散列到相同的值)。
    【解决方案3】:

    我认为您可以合并上述两个想法:使用类似哈希的技术来获取数据的“纯表达式”部分的哈希,并将此哈希用作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
    

    【讨论】:

    • 这是一个很好的建议,但不幸的是我的数据存储方式我认为我无法将纯表达式与大多数唯一数据分开,因为它们是相互递归的结构
    • @Waneck:我添加了一个小的实现来进行分离,以显示我的想法。
    猜你喜欢
    • 1970-01-01
    • 2012-03-27
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-09
    相关资源
    最近更新 更多