流程控制、传递引用:Scarpet中部分语句的作用

作者:禄存,题图来自推特画师@tkmiz ,侵删
Intro.
在写出了优化又差又难读的宽搜和A*之后,我又参考网上的资料写了一个Heap(堆)结构和一个HashMap(哈希表)结构。在编写的过程中发现了一些和技巧,和大家分享一下,也作为对之前文章的修正。
另外我昨天在 Github 上创建了存放自己写的 Scarpet 脚本的仓库,欢迎大家来交流学习。
字符串传引用
之前说过,Scarpet里的自定义函数都是只能传值,不能传引用的(正所谓byVar和byRef的区别)。但是官方这里提供了一个函数:var(expr)
。官方解释翻译过来是:
返回表达式的字符串值所代表的变量,允许更加类似编程地操作变量。可以像HashMap的键-值一样使用本地变量。也能用在全局变量上。
官方的例子:
a = 1; var('a') = 'foo'; a => a == 'foo'
也就是说,函数会根据字符串找到作用范围内的变量引用,传引用还是可能的,不过要做的是传入变量名字符串,然后在方法内部调用var(expr)
,例如我做的一个简单的根据下标获取Heap中元素的方法:
heap_get(heap_name, index) -> element(var(heap_name), index);
heap_get
方法会在其它的很多函数里被调用到,而这些函数接受的也是字符串的变量名。字符串逐层传递到这里再被统一转换成对变量的正式引用,获取下标对应的值后再逐层传回去。
不过要注意的是,这种传引用方式只对全局变量有效t(g) -> var(g) = 16; a = 0; t('a'); a
的输出是0
,但是t(g) -> var(g) = 16; global_a = 0; t('global_a'); global_a
的输出是16
。

这么一来,传递引用就在某种程度上成为了可能,程序库也能正常地执行。
顺便一提,还有vars(prefix)
能返回包含前缀prefix
的所有变量名(字符串),以及undef(expr)
可以根据表达式字符串内容移除变量或者函数。这两者我还没接触过,但应该也有不小的作用。
自带break功能的循环语句
还记得第一篇文章里说过,Scarpet 语言是不存在break
和continue
语句的。但是作者给first
与for
语句设置了退出功能。例如first(list,expr(_,_i))
的官方解释:
寻找并返回列表中首个能满足表达式
expr
(expr == true == 1
)的值。_
被定义为当前元素的值,_i
被定义值的下标。
官方的例子:
first(range(1000,10000), n=_; !first( range(2, sqrt(n)+1), !(n % _) ) ) => 1009 //1000之后的第一个素数
注意这个例子,我们需要重命名外部
first
中的_
来在内部的first
中调用。
虽然这条语句一般只是用来在查找列表的元素,但是它的功能暗示了可以通过在特定情况下返回true
,而在另一些情况下返回false
,可以使流程提前中断
例如在HashMap中,删除键值的函数需要搜索哈希码(hashcode)对应的保存键-值对的列表。这部分是这样写的:
'前面是一些检查键值是否合理的语句';
first(list,
if (element(_, 0) == key,
'这里会有一些删除操作';
true
) != null,
先用first()
遍历列表,一旦列表中键-值对的键与要查询的键匹配,进行删除操作,返回一个true
来提前中断循环。如果键不匹配就继续查找。
另外,因为first
会在未查询到的情况下返回一个null
,在最后进行检测就可以判断是否成功进行了一次删除。这里要注意的是:虽然null
会被看作false
,但是如果第一个元素就查找成功,返回的0
也会被误判成false
,所以必须要对null
加以判断。
同样的,for(list,expr(_,_i),exit(_,_i)?)
把退出判断放到了第三个参数上,是可选的。for
会返回成功迭代的次数;而first
返回找寻到的列表元素,找不到返回null
。因为两者的性能相差无几,可以自行根据功能、易读性和编程习惯进行选用。例如我在堆查找里就使用了for
循环:
heap_search(heap_name, node) -> (
result = -1;
for (var(heap_name), , if (compare_node(_, node) == 0, result = _i; true, false));
result
);
一旦查找到,就把结果设置成当前下标并退出。这样就很简单地实现了在查询到的情况下返回下标,否则返回-1的功能。
这样一来,既可以规避使用return()
带来的性能损耗(上一篇文章说过),又可以相对简洁地对循环的流程进行控制。