我从Icon编程语言中所学到的
在 20 世纪 70 年代末和 80 年代初期,Icon 编程语言主要由拉尔夫·格里斯沃尔德(Ralph Griswold)所设计的。在 60 年代,格里斯沃尔德负责过第一个专门用于处理文本的编程语言 ――Snobol(String Orientated Symbolic Language)。Icon 是基于 Snobol 思想而设计的下一代语言,然而它更加统一和完整。
在很多方面﹐Icon 是第一个“脚本语言”。它是一种非常高级别,具备出色的能力去处理文本数据并与其环境相结合的语言。迄今为止,它都是如此超前,这也许是它从没有很流行的一个原因。早在 Perl 和 TCL(这些语言开启了脚本语言的热潮)崛起之前好几年,它就已经出现了。
对于一个泡在 BASIC、Fortran、PL/I、Pascal 和C里成长起来的人,Icon 就是个彻头彻尾的异类。然而它教会了我许多至今都很有用的东西。
空集(Nullology)
当你调用一个 Icon 函数,它会做以下两件事之一:它可以返回一个值,或者它会失效。失效听上去类似于现代的异常处理机制,但是 Icon 的失效有以下几个不同。首先,当一个 Icon 函数失效时,没有指标说明它为什么失效。再者,失效是预期的。它总在发生,而且是 Icon 工作方式的一个重要部分。(它有一个单独的、原始的、针对真正异常条件的错误处理机制。)
许多 Icon 的函数都是生成器(generator)――它们可以返回多个值。在某些情况下,Icon 将持续调用该函数直到它失效。所以在 Icon 里,失效真正的意思是“没有更多的值”。例如,下面是一个完整的 Icon 程序,把它的输入复制到它的输出:
every write (read ())
子句 Every 表示“这样做直到它失效”。write ()把它的参数写到标准输出,read ()从标准输入返回连续的行,最终在达到文件结束时失效。
在C语言里,失效通常被一些特殊保留的返回值,或者其他特设机制所表示(那些失效指标都太容易被遗漏)。直接从C转到 Icon 后,上面那个小程序惊醒了我,让我见识到了简洁、传神的美感。
第一课
重要的是去区分有和没有。或者是一个函数的返回值,或者是一个变量的值,重要的是要能说“没有一个”。
这一课帮我在几年后认识到异常,也使我领会到其它语言,例如 Lisp, Smalltalk, Ruby 和(在某种程度上)Java,它们的变量包含可以为空的引用,而不是直接存储数据。
一致性(Uniformity)
Icon 是第一个让我用表达式语言做严肃工作的语言。也就是说,Icon 没有语句,只有包含结果值的表达式。我第一次看到这个:
sign := if count > 0 then 1 else -1
起初我真的很困惑,但很快我就明白是怎么回事。在 Icon 里,我所用来思考的“if 语句”事实上只是一个“if 表达式”,就像在别处一样,它会有一个结果值。为什么不应该呢?我曾经思考过这个问题,语句和我所熟悉的语言里的表达式之间的区别好像是人为的和任意的。进一步使用 Icon 的经历证明了这一点。这当然可能造成滥用构造,产生费解的代码。但是有时它们正是你做正确的事情所需要的。
第二课
通常情况下,你认为根本不相同的事情事实上完全相同。不要想当然认为你在某处学到的就是普遍真理。
这么多年来,这一课帮助我用许多新东西迅速武装我的头脑,包括语言,工具,编程范式和平台。
事实(Truth)
我已经提到了生成器和失效的语义。然而一旦你有可以返回多个值甚至失效的表达式,就出现一个问题――类似 E1 E2 这样的表达式是什么意思?在多个值有意义的上下文,你可能需要一个定义;但是在传统环境下,你可能需要一个不同的。所以看起来熟悉的结构就像人们所习惯的。我不会讨论细节(毕竟这不是一节 Icon 课),但是 Icon 设计师们能够找到一个能工作在传统结构和新结构两种情况下的表达式,Icon 思路共存,没有特殊情况。所以,你可以做所有这些事情:
if (i = 1 i = 0) then ...if i = (10) then ... every write (read ("header") read ("body") read ("footer"))
并且它们实现你所期待的。在上例中,“”像一个连接操作符,结果是连接那三个文件。但是操作符的实际语义是不变的――所有这三个例子使用单一的“”定义,结合生成器,表达式失效和目标导向评估。
第三课
我们所受的关于编程的教育正如我们所学的科学――很方便想到世界是如何真正运转,但事实是我们就是不知道。科学给予我们的是似乎能解释世界的理论,因为它们符合所有我们所能想到的实验。但是随着我们对世界的了解不断扩大,迟早我们会认识到之前的理论只是一个近似。一旦事情变得足够快,足够大,牛顿是不够的,我们需要爱因斯坦。(然后它们变得足够小,我们就需要普朗克和波尔)。单值函数,布尔逻辑是足够的,但当把生成器投向混合后,你就需要更多地东西。
这节课已经帮了我很多次去处理复杂、混乱、有时前矛后盾的商业规则和需求。受 Icon 例子的启发,我经常成功找到更深、更简单、更普遍的规则,能作为纯粹的变化支持所有表面的合并。
表达(Representation)
当遇到 Icon 时,我还是一个 Unix 用户,我已经相当精通用作文本模式的正则表达式。Icon 有一种极其复杂的文本模式机制,不是基于正则表达式,但是实际上更强大。(经过 Perl 6 几层经典正则表达式添加,Perl 的正则表达式最终实现了威力相当于 Icon 模式的功能。但那是另一个故事了。)
语言和协议往往用看起来像简单的算数表达式的语法来描述:
X := T T "+" X T := E E "*" T E := "x" "y" "z" "(" X ")"
(仅支持变量x, y 和z的加法和乘法。)
尽管很强大,但不可能实现一个像使用经典的正则表达式那样的语法解析器。(如果你以某种方式成功,也将会非常困难去维护和扩展该解析器。)
但是使用 Icon 的可编程模式机制,你可以实现这样的解析器:
procedure X () suspend [T()] [T(), ="+", X ()] end procedure T () suspend [E()] [E(), ="*", T ()] end procedure E () suspend [="x" ="y" ="z"] [="(", X (), =")"] end
哇!这看起来就像语法!事实上会真的很容易去用语法编写程序以生成解析器!
你可以这样调用它:
parseTree = line ? {X()}
Icon 里的 yacc 是一个为期一天的黑客。
第四课
语法很重要。如果语法适合问题域(problem domain),程序则很容易理解。这就是为什么如果一种语言要很好的处理繁杂的数据时,即使它是一个危险的语言功能、容易被滥用,但操作符重载是必不可少的。强大的正则表达式看上去就像“正则表达式”。
这一课让我知道什么时候去写一点特定领域语言,而不是试图挤入我现有语言的领域。(通常这是一个很容易的事情。)它把我推向动态的、可塑的语言,如 Ruby(我敢说还有 Lisp)更主流的东西。
(语法很重要这一课也大大加强――但以消极的方式――在每次我使用 XSLT 时。幸运的是,很快我就能使用 XQuery 去做所有现在得用 XSLT 去做的工作。)
英文原文:What I Learned from Icon 翻译:@dryrun 译文链接