【问题标题】:Statically "extend" a record-ish data type without indirection hassle静态“扩展”一个创纪录的数据类型,没有间接麻烦
【发布时间】:2012-03-11 21:15:00
【问题描述】:

我目前正在处理一个三级流程,我需要一些信息来进行访问和更新。信息也是三级的,这样一个级别的进程可能需要访问/更新其级别和更高级别的信息。

type info_0 = { ... fields ... }
type info_1 = { ... fields ... }
type info_2 = { ... fields ... }

fun0 将使用info_0 做一些事情,然后将它与info_1 一起传递给fun1,然后取回生成的info_0 并继续,调用另一个fun1 和另一个@987654328 @。 同样的情况也发生在较低的级别。


我目前的代理有

type info_0 = { ... fields ... }
type info_1 = { i0: info_0; ... fields ... }
type info_2 = { i1: info_1; ... fields ... }

fun2 中,更新info_0 变得相当混乱:

let fun2 (i2: info_2): info_2 =
  {
    i2 with
      i1 = {
        i2.i1 with
          i0 = update_field0 i2.i1.i0
      }
  }

更简单的是:

type info_0 = { ... fields ... }
type info_1 = { ... fields ... }
type info_2 = { ... fields ... }
type info_01 = info_0 * info_1
type info_012 = info_0 * info_1 * info_2

let fun2 (i0, i1, i2): info_012 =
  (update_field0 i0, i1, i2)

最后的解决方案看起来不错吗?

对于这类问题有更好的解决方案吗? (例如,我可以编写一个可以处理更新field0 的函数,无论它是处理info_0info_1 还是info_2

OCaml 模块有帮助吗? (例如,我可以在 Sig1 中包含一个 Sig0...)

【问题讨论】:

    标签: functional-programming nested ocaml sml records


    【解决方案1】:

    您需要的是一种更新嵌套不可变数据结构的惯用方式我不知道 OCaml 的任何相关工作,但是Scala/Haskell 中有一些可用的技术,包括 ZippersTree rewriting功能镜头

    Cleaner way to update nested structures

    Is there a Haskell idiom for updating a nested data structure?

    对于 OCaml 的后代 F#,functional lenses 提供了一个很好的解决方案。因此,镜头是这里最相关的方法。你可以从这个帖子中得到使用它的想法:

    Updating nested immutable data structures

    因为 F# 记录语法与 OCaml 几乎相同。

    编辑:

    正如@Thomas 在他的评论中提到的,OCaml 中有完整的镜头实现here。特别是,gapiLens.mli 是我们的兴趣所在。

    【讨论】:

    • 谢谢,bugsquash.blogspot.com/2011/11/lenses-in-f.html 正好解决了我的问题!
    • 是的,确实如此。我希望有人会建立一个类似的库来在 OCaml 中的一般情况下工作。
    • 不满意的是,在我的调用站点上,我仍然需要记住我在哪里调用正确的函数,比如 bookEditorCarMileage.Set 或只是 editorCarMileage.SetcarMileage.Set...类方法和继承我可以obj#set_mileage 不管obj 是什么。
    • 我承认你仍然需要用这种方法编写样板代码。但我更喜欢保持不可变,管理类层次结构的可变更新是一个巨大的痛苦。当我使用函数式语言时,我会不惜一切代价避免变异。
    • 如果你想要镜头,你可以找到一个相当完整的实现here
    【解决方案2】:

    您似乎希望能够将更复杂的值视为更简单的值。这(或多或少)是 OO 模型的本质。除非我真的需要它,否则我通常会尽量避免使用 OCaml 的 OO 子集,但它似乎确实可以满足您的需求。您将有一个对应于info_0 的基类。类info_1 将是info_0 的子类,info_2 将是info_1 的子类。无论如何,这值得考虑。

    【讨论】:

    • OCaml 的对象模型能否很好地适应当前代码的不可变特性?如果可变性不是问题,也可以就地修改记录...
    • 这是一个很好的观点。我在考虑子类型而不是可变性。当然,还有其他需要考虑的事情。
    • 这看起来是一个不错的解决方案:我失去了记录访问语法,但继承的好处是(我可以在类 info_0 中定义一个 getter/setter,并在 info_1 中继承它)。但是,我忘了提到我需要不可变的更新,因为我有时需要执行非确定性过程然后回溯(只保留其中一个),我不想自己处理这个......所以@ FabriceLeFessant 提到,这并不能真正解决我的问题。
    • @Ptival OCaml 对象可以是不可变的,就像记录一样。可能存在的问题是,在 Jeffrey 的模型中,对象会复制信息而不是相互指向,因此您的数据最终可能会使用更多的内存。
    • @Gilles 我需要功能更新,共享,这可能吗?
    【解决方案3】:

    As Jeffrey Scofield suggested,您可以通过使用类来省去使用和更新时间的麻烦:将info_1 设为派生类和info_0 的子类型,等等。

    class info_1 = object
      inherit info_0
      val a_1_1 : int
      …
      method update_a_1_1 v = {<a_1_1 = v>}
    end
    …
    let x : info_1 = new info_1 … in
    let y = x#update_a_1_1 42 in
    …
    

    这种直接对象方法的缺点是,如果您更新任何字段,对象中的所有数据都会被复制;您不能在xy 之间共享info_0 片段。

    您可以使用对象并将设计中的共享行为与记录一起使用,但定义时的样板和恒定的运行时开销会变得更大。

    class info_1 = object
      val zero : info_0
      method get_a_0_1 = zero#get_a_0_1
      method update_a_0_1 = {<zero = zero#update_a_0_1>}
      val a_1_1 : int
      method get_a_1_1 = a_1_1
      method update_a_1_1 v = {<a_1_1 = v>}
    end
    let x : info_1 = new info_1 … in
    let y = x#update_a_1_1 42 in
    …
    

    【讨论】:

      猜你喜欢
      • 2012-11-20
      • 1970-01-01
      • 1970-01-01
      • 2018-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-13
      • 2011-03-31
      相关资源
      最近更新 更多