我目前正在使用一个三级流程,我需要一些信息来访问和更新流程。信息也是三级的,这样,一个级别的进程可能需要在其级别和更高级别访问/更新信息。
type info_0 = { ... fields ... }
type info_1 = { ... fields ... }
type info_2 = { ... fields ... }
fun0
会用info_0
做一些事情,然后将其与info_1
一起传递给fun1
,然后取回结果info_0
并继续,用另一个info_1
调用另一个fun1
。同样的情况也发生在较低级别。
我目前的代表有
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_0
、info_1
还是info_2
(
OCaml 模块会有所帮助吗?(例如,我可以在Sig1
中包含Sig0
...
您需要的是一种更新嵌套不可变数据结构的惯用方法。我不知道OCaml中的任何相关工作,但是Scala/Haskell中有一些可用的技术,包括Zippers,Treerewrite和Functional Lenses:
更新嵌套结构的更简洁方法
是否有用于更新嵌套数据结构的 Haskell 惯用语?
对于 OCaml 的后代 F#,功能性镜头提供了一个不错的解决方案。因此,镜片是这里最相关的方法。您可以从此线程中获得使用它的想法:
更新嵌套的不可变数据结构
因为 F# 记录语法与 OCaml 几乎相同。
编辑:
正如@Thomas在他的评论中提到的,这里有一个完整的OCaml镜头实现。特别是,gapiLens.mli是我们感兴趣的。
您似乎希望能够将更复杂的值视为更简单的值。 这(或多或少(是OO模型的本质。 除非我真的需要它,否则我通常会尽量避免使用 OCaml 的 OO 子集,但它似乎确实满足了您在这里的需求。 您将有一个对应于 info_0
的基类。 类info_1
将是info_0
的子类,而info_2
将是info_1
的子类。 无论如何,这值得考虑。
正如 Jeffrey Scofield 所建议的那样,您可以使用类来节省使用和更新时间的麻烦:使 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
…
这种直接对象方法的缺点是,如果更新任何字段,则会复制对象中的所有数据;您无法在x
和y
之间共享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
…