编程语言中最基本的事情就是数据变换, 比如基本的四则运算, 或是求正弦, 或是将字符串拼接起来, 前面这三种都是针对数据个体的, 而且在不同语言中实现得大同小异. 而对于数据集体的操作, 比如对一个列表进行变换, 各种编程语言会有各自独特的方法, 甚至同一种语言在语言特性升级前后都会有不同, 比如在 C++ 中, 一直以来采用
for
来迭代, 在 C++11 新标准引入了 lambda 之后, std::for_each
就咸鱼翻身变得流行起来; 又比如在 Python 中, 列表迭代采用 for element in list
的方式, 而在引入 yield
之后的版本中, 转为了 for element in generator
. 虽然语法没有变, 但是整体思想改变了不少.而在这些眼花缭乱迭代方式当中, 程序员究竟想要得到什么? 解决实际问题的过程中, 最常见的操作应当是
- 映射: 给出一个函数 f 和集合 {x}, 得到新的集合 {f(x)}
- 过滤: 给出一个函数 g 和集合 {x}, 得到新的集合 {x 当且仅当 g(x) 为真}
将这些需求提出来之后, 最让我感到有趣的莫过于 Shell 的管道. 比如这样一句
find -type f | awk '{ print "mv", $1, "/tmp/" }'
相当与进行了一次映射, 集合为 find -type f 给出的文件列表, 函数 f 就是 awk 程序. 而这个例子
find -type f | grep wav$
则是一个过滤.
上面的例子中, 并没有任何迭代的过程, 可以说 Shell 的管道完全抽象了集合迭代, 让对集合的操作变为了对元素个体的操作. 在语言层面上将迭代过程本身封装起来, 这确实是一件很实在的事情. 因此, 我很乐意将这个特性引进到了 Stekin 程序设计语言中.
在当前的 dev 分支上, Stekin 实现了类似管道的列表操作方式. 具体的语法类似
列表 | return 表达式
列表 | if 布尔表达式
在 awk 中, 使用
$1
等表示该行的第一个词, 而 $2
表示第二个, 等等. 而在 Stekin 中并没有这个困扰, 因为迭代的列表中元素类型是确定的, 因此直接使用 $element
来表示元素, 如下面的代码list: [0, 1, 2, 3, 4]
sqr_list: list | return $element * $element
$element
映射到其平方 $element * $element
, 得到的 sqr_list
将是 [0, 1, 4, 9, 16]
.除了在管道中可以使用
$element
引用元素之外, 还可以使用 $index
引用当前元素的索引 (从 0 开始). 不过这种表示法仍然有待商榷, 我曾想过使用如 $$
, $@
这样看起来很 Shell 很 Makefile 的符号, 但那样实在太不直观, 于是使用了 $element
这样直接写单词的方法. 如果你有什么想法或建议, 欢迎在回复中提出来.而过滤操作则除了将管道符号
|
之后的 return
关键字换成 if
之外, 它要求接下来的表达式是布尔类型的. 当且仅当这个表达式的值为真时, 则该元素将被添加到结果列表中去, 如list: [0, 1, 2, 3, 4]
new_list: list | if $element % 3 != 0
new_list
将是 [1, 2, 4]
.与 Shell 类似, Stekin 中的管道也是可以任意追加的, 如
list: [0, 1, 2, 3, 4]
mod_list: list | return $element * $element | return $element % 7
[0, 1, 4, 2, 2]
.更多例子和说明可以在 Stekin dev samples 中找到.
最后, 祝大家新学期新气象, 在这个学年里快乐生活, 快乐学习, 快乐工作.