提问人:Kris 提问时间:4/24/2022 更新时间:5/7/2022 访问量:283
有没有办法获得对可变结构体字段的“引用”
Is there a way to obtain a 'reference' to a mutable struct field
问:
所以我有一个带有可变字段的记录类型:
type mpoint = { mutable x:int ; mutable y: int };;
let apoint = { x=3 ; y=4};;
我有一个函数,它需要一个“ref”,并对其内容做一些事情。 例如:
let increment x = x := !x+1;;
val increment : int ref -> unit = <fun>
有没有办法从可变字段中获取“引用”,以便我可以将其传递给函数。即我想做这样的事情:
increment apoint.x;; (* increment value of the x field 'in place' *)
Error: This expression has type int but an expression was expected of type
int ref
但上述方法不起作用,因为返回字段的值而不是其“ref”。如果这是 golang 或 C++,也许我们可以使用运算符来指示我们想要地址而不是字段的值:。apoint.x
&
&apoint.x
(如何)我们可以在Ocaml中做到这一点?
PS:是的,我知道避免以这种方式使用副作用可能更常见。但我保证,我这样做是有充分理由的,因为它比这个简化/人为的例子可能暗示的更有意义。
答:
没有办法完全按照你的要求去做。引用的类型非常具体:
# let x = ref 3
val x : int ref = {contents = 3}
引用是具有一个名为 的可变字段的记录。你不能从其他记录的任意可变字段中真正捏造出来。即使你愿意对类型系统撒谎,记录的字段也与记录的表示方式完全不同。contents
您可以将字段声明为实际引用:
type mpoint = { x: int ref; y: int ref; }
那就没有问题了,真的是参考了。但这种表示效率不高,即它需要更多的内存,并且有更多的取消引用来访问值。apoint.x
如果 API 是以命令式风格设计的,那么在 OCaml 中将很难使用。反正我就是这么看的。另一种说法是 int 很小。接口可能应该接受一个 int 并返回一个新的 int,而不是接受对 int 的引用并就地修改它。
评论
您可以随时临时复制字段的内容,调用该函数,然后再返回:
let increment_point_x apoint =
let x = ref apoint.x in
increment x;
apoint.x <- !x
当然没有它所能达到的效率(也不优雅),但它有效。
评论
不可能完全按照问题的要求去做(@JeffreyScofield解释了原因,所以我不会重复)。已经提出了一些解决方法。
这是另一种可能有效的解决方法,如果您可以将函数的实现更改为使用“自制”ref 类型。这与要求非常接近。increment
我们可以定义自己的引用类型,而不是让它采用“内置”引用。“参考”的精神是你可以设置和获得的东西。因此,我们可以将其表征/表示为 a 和 function 的组合。get
set
type 'a ref = {
set: 'a -> unit;
get: unit -> 'a;
};;
type 'a ref = { set : 'a -> unit; get : unit -> 'a; }
我们可以定义这种类型的通常和运算符:!
:=
let (!) cell = cell.get ();;
val ( ! ) : 'a ref -> 'a = <fun>
let (:=) cell = cell.set;;
val ( := ) : 'a ref -> 'a -> unit = <fun>
增量函数的代码可以保持不变,即使它的类型“看起来”相同(但它微妙地“不同”,因为它现在使用我们自己的类型而不是内置的 ref)。ref
let increment cell = cell := !cell + 1;;
val increment : int ref -> unit = <fun>
当我们想要引用一个字段时,我们现在可以创建一个。例如,引用 x 的函数:
let xref pt = {
set = (fun v -> pt.x <- v);
get = (fun () -> pt.x);
};;
val xref : mpoint -> int ref = <fun>
现在我们可以调用 x 字段:increment
increment (xref apoint);;
- : unit = ()
Jeffrey Scofield 从类型系统的角度解释了为什么不能在 ocaml 中完成此操作。
但你也可以从GC(垃圾回收器)的角度来看它。在 ocaml 内部,所有内容都是存储为 31/63 位值的平凡类型(int、bool、char 等),或者是指向内存块的指针。每个内存块都有一个标头,用于描述 GC 的内容,并具有 GC 使用的一些额外位。
当您在内部查看引用时,它是指向包含带有 .通过该指针,GC 可以访问标头,并知道内存块仍然可以访问。mutable contents
但是,让我们假设您可以传递给一个引用的函数。然后在内部,指针将指向 的中间,当 GC 尝试访问该块的标头时,它将失败,因为它不知道标头与指针的偏移量。apoint.y
apoint
现在如何解决这个问题?
已经提到的一种方法是使用引用而不是可变。另一种方法是使用吸气器和二传器:
# type 'a mut = (unit -> 'a) * ('a -> unit);;
type 'a mut = (unit -> 'a) * ('a -> unit)
# type mpoint = { mutable x:int ; mutable y: int };;
type mpoint = { mutable x : int; mutable y : int; }
# let mut_x p = (fun () -> p.x), (fun x -> p.x <- x);;
val mut_x : mpoint -> (unit -> int) * (int -> unit) = <fun>
# let mut_y p = (fun () -> p.y), (fun y -> p.y <- y);;
val mut_y : mpoint -> (unit -> int) * (int -> unit) = <fun>
如果你只想变量,你可以传递一个增量函数而不是 getter/setter。或任何其他帮助程序函数的集合。getter/setter pait 只是最通用的接口。incr
评论
!
:=
上一个:在 Rust 中返回可变映射引用
下一个:在结构中改变数组的最佳方法?
评论
mutable
ref
apoint.x