鉴于Rascal数据的不变性,如果以后的操作取决于早期的结果,那么这些数据的首选方法是什么?
例如,将注释值分配给树中的每个节点,其中较高节点的值取决于较低节点的值。如果您编写具有多个案例的单个访问语句,则较低级别的插入语句不会更改树,因此更高级别的操作可能没有任何操作。另一方面,使用visit语句包围每个case语句 - 并在每次访问后将树变量重新绑定到新树 - 是很麻烦的,更糟糕的是,似乎使结果取决于语句的顺序。
答案 0 :(得分:0)
微妙的问题:-) visit
语句有一个微妙的语义,特别是从OO角度来看它。虽然它正在积极地遍历一个价值,但它正在重建一个新的价值。取决于其案例陈述中的遍历顺序匹配的策略(顺序)"参见"不同的东西。
有时候想象一下访问作为代码生成器会产生一组相互递归的函数,这些函数将被访问的部分作为参数并在返回时返回一个新值。访问的主体(案例)变成了switch语句,这是这些生成函数的核心。然后,根据遍历顺序,递归步骤位于(bottom-up
)之前或此切换语句(top-down
)之后:
// bottom-up visit
x f(value x) {
newChildren = map(f, children of x);
x = newX(x, newChildren);
switch (x) {
case y : return whatever(y);
}
}
因此,bottom-up
次访问的切换案例中的代码会看到递归调用生成的树(尽管实际上没有更新树)。
以下是一个例子:
rascal>data T = tee(T, T) | i(int i);
data T = tee(T, T) | i(int i);
ok
rascal>t = tee(tee(i(1),i(2)),tee(i(3),i(4)));
t = tee(tee(i(1),i(2)),tee(i(3),i(4)));
T: tee(
tee(
i(1),
i(2)),
tee(
i(3),
i(4)))
rascal>visit(t) {
>>>>>>> case i(x) => i(x+1)
>>>>>>> case tee(i(x),y) => tee(i(x+1),y)
>>>>>>>}
T: tee(
tee(
i(3), // here you see how 1 turned into 3 by incrementing twice
i(3)), // increment happened once here
tee(
i(5), // increment happened twice here too
i(5))) // increment happened once here
您可以看到某些节点有两次增量,因为第二种情况匹配
已经访问了一个tee
之后的i
个子节点,以返回已经递增的另一个i
节点。
尝试其他策略会为您提供其他结果,请参阅http://tutor.rascal-mpl.org/Rascal/Rascal.html#/Rascal/Expressions/Visit/Visit.html。请注意,访问语句范围内的变量由所有访问级别的所有访问共享,这样可以模拟类似拉链的行为(您可以始终将以前访问过的节点存储在临时)。
顺便说一句,语言设计试图避免需要更多涉及的功能性编程设计模式"比如拉链,因为它们使类型系统和人们与它交互的方式变得复杂。为了使这些事情在访问中正确地工作,在一个异构数据类型上进行递归,你需要一个多态学博士来理解它的类型是否正确。秘密地说,访问语句模拟了一组内置的类型安全的rank-2高阶多态函数,但这些都在幕后。