有没有办法获得可变结构字段的 'reference'
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>
有没有办法从可变字段中获取 'reference',以便我可以将其传递给函数。 IE。我想做类似的事情:
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
但是上面的方法不起作用,因为 apoint.x
returns 字段的值不是它的 'ref'。如果这是 golang 或 C++,也许我们可以使用 &
运算符来指示我们想要地址而不是字段的值:&apoint.x
.
(如何)我们可以在 Ocaml 中做到这一点?
PS:是的,我知道以这种方式避免使用副作用可能更常见。但我保证,我这样做是有充分理由的,因为它比这个 simplified/contrived 示例所暗示的更有意义。
无法完全按照您的要求进行操作。引用的类型非常具体:
# let x = ref 3
val x : int ref = {contents = 3}
引用是一条记录,其中包含一个名为 contents
的可变字段。您不能真正从其他记录的任意可变字段中伪造它。即使你愿意对类型系统撒谎,记录的字段与记录完全不同。
您可以将您的字段声明为实际引用:
type mpoint = { x: int ref; y: int ref; }
那就没问题了,apoint.x
确实是个参考。但是这种表示效率不高,即它需要更多的内存并且有更多的解引用来访问值。
如果 API 是以命令式风格设计的,将很难在 OCaml 中使用。反正我就是这么看的。另一种说法是整数很小。该接口或许应该接受一个 int 和 return 一个新的 int,而不是接受对 int 的引用并就地修改它。
您可以随时复制字段的内容,调用函数,然后再返回:
let increment_point_x apoint =
let x = ref apoint.x in
increment x;
apoint.x <- !x
当然没有尽可能高效(也不优雅),但它有效。
不可能完全按照问题的要求去做(@JeffreyScofield 解释了原因,所以我不会重复)。已提出一些解决方法。
如果您可以更改 increment
函数的实现以使用 'home made' ref 类型,则这是另一个可能有效的解决方法。这与要求的非常接近。
我们可以定义自己的引用类型,而不是让它接受 'built-in' 引用。 'reference' 的精神是可以设置和获取的。所以我们可以 characterise/represent 它作为 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>
增量函数的代码可以保持不变,即使它的类型 'looks' 相同(但它是微妙的 'different' 因为它现在使用我们自己的类型 ref
而不是 built-in 参考).
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、...),要么是指向内存块的指针。每个内存块都有一个 header 向 GC 描述内容,并有一些额外的位供 GC 使用。
当您在内部查看引用时,它是指向内存块的指针,该内存块包含带有 mutable contents
的记录。通过该指针,GC 可以访问 header 并且知道内存块仍然可以访问。
但是我们假设您可以将 apoint.y
传递给采用引用的函数。然后在内部指针将指向 apoint
的中间,当 GC 尝试访问该块的 header 时,GC 将失败,因为它不知道 header 指针的偏移量是多少]位于。
现在如何解决这个问题?
已经提到的一种方法是使用引用而不是可变的。另一种方法是使用 getter 和 setter:
# 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>
如果您只想 incr
变量,您可以传递一个增量函数而不是 getter/setter。或任何其他 collection 辅助函数。 getter/setter pait 只是最通用的界面。
所以我有一个带有可变字段的记录类型:
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>
有没有办法从可变字段中获取 'reference',以便我可以将其传递给函数。 IE。我想做类似的事情:
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
但是上面的方法不起作用,因为 apoint.x
returns 字段的值不是它的 'ref'。如果这是 golang 或 C++,也许我们可以使用 &
运算符来指示我们想要地址而不是字段的值:&apoint.x
.
(如何)我们可以在 Ocaml 中做到这一点?
PS:是的,我知道以这种方式避免使用副作用可能更常见。但我保证,我这样做是有充分理由的,因为它比这个 simplified/contrived 示例所暗示的更有意义。
无法完全按照您的要求进行操作。引用的类型非常具体:
# let x = ref 3
val x : int ref = {contents = 3}
引用是一条记录,其中包含一个名为 contents
的可变字段。您不能真正从其他记录的任意可变字段中伪造它。即使你愿意对类型系统撒谎,记录的字段与记录完全不同。
您可以将您的字段声明为实际引用:
type mpoint = { x: int ref; y: int ref; }
那就没问题了,apoint.x
确实是个参考。但是这种表示效率不高,即它需要更多的内存并且有更多的解引用来访问值。
如果 API 是以命令式风格设计的,将很难在 OCaml 中使用。反正我就是这么看的。另一种说法是整数很小。该接口或许应该接受一个 int 和 return 一个新的 int,而不是接受对 int 的引用并就地修改它。
您可以随时复制字段的内容,调用函数,然后再返回:
let increment_point_x apoint =
let x = ref apoint.x in
increment x;
apoint.x <- !x
当然没有尽可能高效(也不优雅),但它有效。
不可能完全按照问题的要求去做(@JeffreyScofield 解释了原因,所以我不会重复)。已提出一些解决方法。
如果您可以更改 increment
函数的实现以使用 'home made' ref 类型,则这是另一个可能有效的解决方法。这与要求的非常接近。
我们可以定义自己的引用类型,而不是让它接受 'built-in' 引用。 'reference' 的精神是可以设置和获取的。所以我们可以 characterise/represent 它作为 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>
增量函数的代码可以保持不变,即使它的类型 'looks' 相同(但它是微妙的 'different' 因为它现在使用我们自己的类型 ref
而不是 built-in 参考).
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、...),要么是指向内存块的指针。每个内存块都有一个 header 向 GC 描述内容,并有一些额外的位供 GC 使用。
当您在内部查看引用时,它是指向内存块的指针,该内存块包含带有 mutable contents
的记录。通过该指针,GC 可以访问 header 并且知道内存块仍然可以访问。
但是我们假设您可以将 apoint.y
传递给采用引用的函数。然后在内部指针将指向 apoint
的中间,当 GC 尝试访问该块的 header 时,GC 将失败,因为它不知道 header 指针的偏移量是多少]位于。
现在如何解决这个问题?
已经提到的一种方法是使用引用而不是可变的。另一种方法是使用 getter 和 setter:
# 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>
如果您只想 incr
变量,您可以传递一个增量函数而不是 getter/setter。或任何其他 collection 辅助函数。 getter/setter pait 只是最通用的界面。