kin*_*juf 6 reference sml smlnj memoization
假设我们有以下玩具二叉树结构:
datatype Tree = Leaf | Branch of Tree * Tree
fun left(Branch(l,r))= l
fun right(Branch(l,r))= r
Run Code Online (Sandbox Code Playgroud)
并假设我们有一些大而昂贵的计算树
val c: Tree= …
val d: Tree= Branch(c,c)
Run Code Online (Sandbox Code Playgroud)
我们能否在 SML/NJ 解释器中验证left(d)并right(d)确实引用内存中的同一个位置?
(这个问题源于使用可能包含循环的惰性流,并试图调试记忆是否正常工作。)
我认为我们可以通过将两个值转换为wordusing来实现这一点Unsafe.cast,它将指针重新解释为可以与 进行比较的数字=。这是一个is实现这个想法的函数:
infix 4 is (* = > < >= ... *)
fun op is(a: 'a, b: 'a) = (Unsafe.cast a: word) = Unsafe.cast b
Run Code Online (Sandbox Code Playgroud)
注意:
(a, b)以确保类型检查器将参数限制为同一类型,因为否则的 typesigis将是'a * 'b -> boolUnsafe.cast应用程序以防止 SML/NJ 必须使用polyEqual,从而避免发出Warning: calling polyEqual这是一个说明向量中结构共享的示例:
local
fun const a _ = a
val v = Vector.tabulate(1000, const #[1,2,3])
val v = Vector.update(v, 230, #[1,2,3]) (* same value, but new allocation *)
in
val test1 = Vector.sub(v, 0) is Vector.sub(v, 999)
val test2 = Vector.sub(v, 0) is Vector.sub(v, 230)
end
Run Code Online (Sandbox Code Playgroud)
它确实按预期工作。回复者回答为
(* val test1 = true : bool
val test2 = false : bool *)
Run Code Online (Sandbox Code Playgroud)
现在这是您问题中的树示例:
local
datatype Tree
= Leaf
| Branch of Tree * Tree
fun left (Branch(l,r)) = l
fun right (Branch(l,r)) = r
val c = Branch(Leaf,Leaf) (* imagine it being something complex *)
val d = Branch(c, c)
in
val test3 = left d is right d
end
Run Code Online (Sandbox Code Playgroud)
当我们尝试时,它会正确回答:
(* val test3 = true : bool *)
Run Code Online (Sandbox Code Playgroud)
我想这回答了你的问题。在这一点下面,我将讨论选择word以及当我们转换为它时内部可能发生的事情
据我所知,SML/NJ 像许多 lisp、v8、OCaml 等一样进行指针标记。这意味着我们想要专门转换为非堆分配的类型。因为我们希望能够读取指针值,不要误解堆对象。
我认为word对于这个目的来说效果很好;它像 int 一样是直接的,并且与它不同的是无符号的..所以它应该对应于内存地址(不要阻止我)。
似乎有一个 bug* 阻止您直接在 repl 中检查单词值,这可能是指针标记在起作用。
* 至少编译器是这样报告的?自 v110.99 起
一种解决方法是立即将值转换为不同的表示形式(也许需要装箱?),例如字符串或 Word64.word
fun addrOf x = Word.toString (Unsafe.cast x)
Run Code Online (Sandbox Code Playgroud)
事实上,当我们尝试使用新定义的addrOf函数来比较地址与其字符串化值时,我们可以观察指针标记的效果
(* We'll need these definitions onwards, might as well have them here: *)
infix 5 >> <<
val op >> = Word.>>
val op << = Word.<<
val unsafeWord = Option.valOf o Word.fromString
Run Code Online (Sandbox Code Playgroud)
local
val x = SOME 31 (* dummy boxed value *)
val addr = unsafeWord (addrOf x)
in
val test4 = Unsafe.cast x = addr
val test5 = Unsafe.cast x >> 0w1 = addr >> 0w1 (* get rid of lowest bit *)
end
(* val test4 = false : bool
val test5 = true : bool *)
Run Code Online (Sandbox Code Playgroud)
那么,如果标签只是 SML/NJ 中机器字的最低位,就像在许多标记指针实现中一样,那么准确地说,指针应该是转换值向右移动一次,然后向左移动再次。
fun addrOf x = Unsafe.cast x >> 0w1 << 0w1
Run Code Online (Sandbox Code Playgroud)
我们进行这种看似转换的原因nop(记住,所有指针都是偶数)是因为它正确地标记了过程中的转换字值。
如果我们先左移然后右移,当强制指针变成正确的单词时,标签本身会找到第一个操作的值..这就是为什么我们先右移..从零向左移动会填充较低的值位,因此不会丢失有关地址的信息,但内部会正确存在立即值标记。
local
fun strAddrOf x = Word.toString (Unsafe.cast x)
fun isEven x = Word.andb (x, 0w1) = 0w0
val x = SOME 42
val ogAddr = unsafeWord (strAddrOf x) (* a known-correct conversion: no shifting takes place *)
val badAddr = Unsafe.cast x << 0w1 >> 0w1
val goodAddr = Unsafe.cast x >> 0w1 << 0w1
in
val test6 = ogAddr = badAddr
val test7 = ogAddr = goodAddr
val test8 = isEven ogAddr
end
(* val test6 = false : bool
val test7 = true : bool
val test8 = true : bool *)
Run Code Online (Sandbox Code Playgroud)
这种移入addrOf允许您直接获取指针值,而无需中间转换(和装箱)到stringor word64。当然,此解决方案会因实际未装箱的值而崩溃,因此最好测试对象是否Unsafe.boxed在定义中以 ( )开头装箱addrOf,并在您使用立即数的情况下返回 0wx0 。
希望这适合您的目的。到目前为止,这对我来说确实如此!