抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

背景和问题的引入

众所周知,Wolfram语言作为一个极为“高级”的编程语言,并没有提供指针这类较为底层的内存管理手段。而符号本身几乎总是充当了类似引用的作用,比如

1
2
3
4
set[sym_Symbol]:=sym=1
b=a;
set[b];
a

可以看到符号a的值通过set[b]赋为了1。在这里b形式上充当了类似其它语言中的引用的作用。但这种方式并不通用,只要a已经具有值了,这种平凡的方式就不能起作用了。有的读者可能会想到:=或者Hold封装,不过单靠这些也不能简单地对已具有本值的符号进行修改。因此,本文试图提出一种具有类似其它语言中指针语义的封装,实现对符号的间接操作。

预期的目标

这里想实现一种具有类似C语言用法的指针(引用)封装。当然,由于Mathematica没有直接访问内存的手法,所有引用都是针对符号展开的。具体而言,应该可以实现下面的效果:

解引用:

1
2
3
a=1;
b=ref[a];
deref@b

得到1

左值语义:

1
2
3
4
a=1;
b=ref[a];
deref@b=2;
a

得到2

更复杂的左值:

1
2
3
4
a={1,2,3};
b=ref[a];
deref[b][[2]]=4;
a

得到{1,4,3}

多重引用:

1
2
3
4
5
a=1;
b=ref[a];
c=ref[b];
deref@deref@c=2;
a

得到2

这里只是举几个例子,文章毕竟不是测试集,也就不再赘述。

当然,我们希望能更接近C语言指针的语义。在这里,ref相当于C语言中的&单目运算符,而deref则相当于C语言的*单目运算符。

思路与实现

考虑我们需要保有符号,才可能对符号进行引用,因此首先ref必须具有HoldFirst属性。然后deref能在计算时解引用,也就是

1
deref[ref[sym_]] := sym

然后我们需要重载Set以获得左值语义。由于deref可能出现在很深的层次中,单靠TagSetDelayed不能覆盖重载的各种情况。但需要重载的情况必定含有deref,因此可以利用条件模式决定重载策略

1
2
3
Unprotect[Set];
Set[lhs_, rhs_] /; MemberQ[Unevaluated[lhs], _deref, {0, Infinity}] := <<具体实现>>
Protect[Set];

由于引用在这里总是保有着符号,因此左值语义在这里更类似一个宏展开,将=左侧的所有直接或隐含的deref[ref[sym]]结构展开成对应的sym。为了达到这个目的,首先需要保护=的左侧不进行计算,这里只需要一个简单的Hold封装即可。然后上面的所说的“宏展开”细节上应该分两步进行:

一是将“隐含”转换为“显含”:直观上来说,可能大家会写成这样的替换规则:deref[r_]:>deref[Evaluate[r]],但这是不对的。由于Hold封装的存在,需要用到一个名为Trott-Strzebonski的技巧,而且由于前面已经定义了deref[ref[sym_]]:=sym,因此这里必须借助Block局部地清除这个定义才行,另一方面,为了处理多重引用,还需要排除deref[deref[xxx]]这种模式被直接展开。

二是对显式结构进行替换:这部分比较简单,直接使用deref@ref[sym_Symbol]:>sym即可。同样也是因为Hold封装的存在,符号sym即使有本值也不会计算。

最后将展开的结果还原为赋值表达式即可。

综合一下,我们可以得到一个比较完整的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
SetAttributes[ref, HoldFirst]
deref[ref[sym_]] := sym
Unprotect[Set];
Set[lhs_, rhs_] /; MemberQ[Unevaluated[lhs], _deref, {0, Infinity}] :=
With[{
lhs1 = Block[{deref},
Hold[lhs] //. {
deref@ref[sym_Symbol] :> sym,
deref[r : Except[_deref]] :> With[{eval = deref[r]}, eval /; True] (*Trott-Strzebonski*)
}]},
Replace[Hold[Set[lhs1, rhs]], Hold[Set[Hold[lhs2_], rhs2_]] :> Set[lhs2, rhs2]]
]
Protect[Set];

当然,对于一个真正实用的模块而言,还需要一些诸如错误处理之类的细节,但如果只考虑正确使用的话,实现指针语义的核心部分都已经列在这里了。一个相对实用的版本可以在 https://github.com/miRoox/LValueRef 找到。

评论