我从Icon编程语言中所学到的

伯乐在线注:英文原文:格伦•范登伯格,感谢@dryrun 的热心翻译。如果其他朋友也有不错的原创或译文,可以尝试推荐给伯乐在线。以下是译文。

 

在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()从标准输入返回连续的行,最终在达到文件结束时失效。

在C语言里,失效通常被一些特殊保留的返回值,或者其他特设机制所表示(那些失效指标都太容易被遗漏)。直接从C转到Icon后,上面那个小程序惊醒了我,让我见识到了简洁、传神的美感。

 

第一课

重要的是去区分有和没有。或者是一个函数的返回值,或者是一个变量的值,重要的是要能说“没有一个”。

这一课帮我在几年后认识到异常,也使我领会到其它语言,例如Lisp, Smalltalk, Ruby和(在某种程度上)Java,它们的变量包含可以为空的引用,而不是直接存储数据。

 

一致性(Uniformity)

Icon是第一个让我用表达式语言做严肃工作的语言。也就是说,Icon没有语句,只有包含结果值的表达式。我第一次看到这个:

起初我真的很困惑,但很快我就明白是怎么回事。在Icon里,我所用来思考的“if语句”事实上只是一个“if表达式”,就像在别处一样,它会有一个结果值。为什么不应该呢?我曾经思考过这个问题,语句和我所熟悉的语言里的表达式之间的区别好像是人为的和任意的。进一步使用Icon的经历证明了这一点。这当然可能造成滥用构造,产生费解的代码。但是有时它们正是你做正确的事情所需要的。

 

第二课

通常情况下,你认为根本不相同的事情事实上完全相同。不要想当然认为你在某处学到的就是普遍真理。

这么多年来,这一课帮助我用许多新东西迅速武装我的头脑,包括语言,工具,编程范式和平台。

 

事实(Truth)

我已经提到了生成器和失效的语义。然而一旦你有可以返回多个值甚至失效的表达式,就出现一个问题­――类似 E1 | E2 这样的表达式是什么意思?在多个值有意义的上下文,你可能需要一个定义;但是在传统环境下,你可能需要一个不同的。所以看起来熟悉的结构就像人们所习惯的。我不会讨论细节(毕竟这不是一节Icon课),但是Icon设计师们能够找到一个能工作在传统结构和新结构两种情况下的表达式,Icon思路共存,没有特殊情况。所以,你可以做所有这些事情:

并且它们实现你所期待的。在上例中,“|”像一个连接操作符,结果是连接那三个文件。但是操作符的实际语义是不变的――所有这三个例子使用单一的“|”定义,结合生成器,表达式失效和目标导向评估。

 

第三课

我们所受的关于编程的教育正如我们所学的科学――很方便想到世界是如何真正运转,但事实是我们就是不知道。科学给予我们的是似乎能解释世界的理论,因为它们符合所有我们所能想到的实验。但是随着我们对世界的了解不断扩大,迟早我们会认识到之前的理论只是一个近似。一旦事情变得足够快,足够大,牛顿是不够的,我们需要爱因斯坦。(然后它们变得足够小,我们就需要普朗克和波尔)。单值函数,布尔逻辑是足够的,但当把生成器投向混合后,你就需要更多地东西。

这节课已经帮了我很多次去处理复杂、混乱、有时前矛后盾的商业规则和需求。受Icon例子的启发,我经常成功找到更深、更简单、更普遍的规则,能作为纯粹的变化支持所有表面的合并。

 

表达(Representation)

当遇到Icon时,我还是一个Unix用户,我已经相当精通用作文本模式的正则表达式。Icon有一种极其复杂的文本模式机制,不是基于正则表达式,但是实际上更强大。(经过Perl 6几层经典正则表达式添加,Perl的正则表达式最终实现了威力相当于Icon模式的功能。但那是另一个故事了。)

语言和协议往往用看起来像简单的算数表达式的语法来描述:

(仅支持变量x, y和z的加法和乘法。)

尽管很强大,但不可能实现一个像使用经典的正则表达式那样的语法解析器。(如果你以某种方式成功,也将会非常困难去维护和扩展该解析器。)

但是使用Icon的可编程模式机制,你可以实现这样的解析器:

哇!这看起来就像语法!事实上会真的很容易去用语法编写程序以生成解析器!

你可以这样调用它:

Icon里的yacc是一个为期一天的黑客。

 

第四课

语法很重要。如果语法适合问题域(problem domain),程序则很容易理解。这就是为什么如果一种语言要很好的处理繁杂的数据时,即使它是一个危险的语言功能、容易被滥用,但操作符重载是必不可少的。强大的正则表达式看上去就像“正则表达式”。

这一课让我知道什么时候去写一点特定领域语言,而不是试图挤入我现有语言的领域。(通常这是一个很容易的事情。)它把我推向动态的、可塑的语言,如Ruby(我敢说还有Lisp)更主流的东西。

(语法很重要这一课也大大加强――但以消极的方式――在每次我使用XSLT时。幸运的是,很快我就能使用XQuery去做所有现在得用XSLT去做的工作。)

(全文完)

 

英文原文:Glenn Vanderburg,感谢@dryrun 的翻译

译文链接:http://blog.jobbole.com/42136/

【非特殊说明,转载必须在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】

收藏 4 评论

关于作者:dryrun

(新浪微博:@dryrun)!dryrun140131) 个人主页 · 我的文章

相关文章

可能感兴趣的话题



直接登录
最新评论
  • Jak Wings   2013/06/27

    现在竟然还有更新,看来很有趣。

    译文中对坚杠 | 的描述可能不太清楚,通俗地说,| 是用来连接替补元素的,很像 or 逻辑运算符,但更复杂,要结合上下文。
    例如,if (i = 1 | i = 0) 相当于 if i = (1|0)
    every i := (1 to 10) | (91 to 100) do write(p[i]) 即是输出 p[1] 到 p[10] 还有 p[91] 到 p[100]

    • Jak Wings   2013/06/28

      「……失效是预期的。它总在发生……」这话说的的确没错,Icon 真的把这个用到家了,只要不是语法错误,或者是别的运行时内存错误之类的问题,什么「找不到下一个元素」「空值」「判断为假」,都当作失效,然后便寻找替补元素,连错误处理都省了。即使将循环上限设为 5 ,只要读不了下一个值,即使只是循环了 3 次也会自动退出循环,不必处处手动加入判断代码。
      ---

      我看了一下 Icon 的官方说明,尝试说明一下「第三课」里面那个关于 x,y,z 加法和乘法的解析程序:

      line ? {X()} 中的 line 代表一行算式,? 运算符表示用后面的表达式 {X()} 对 line 进行字符串搜索处理,会初始化隐式变量 &subject 为那行算式,&pos 为 1 即第一个字符的索引,搜索操作直到 &pos 超过 &subject 的长度才停止,即是说 X 会被调用多次。{} 表示表达式的组合,和 if { } 的一样,声明范围而已。

      suspend 作用和 return 一样,但其实类似迭代器,会返回得到的结果,下一次调用那个使用 suspend 的函数,会继续返回下一个结果。从这里看来,Icon 的 procedure 挺有趣的,不过得注意内存泄漏。

      | 的作用依然搞不太懂,function 和 procedure 对待它的作用不一样,例如 write(1 | 2) 只会输出 1 和一个空行,procedure mywrite(i) { write(i) } 则会分别输出 1 和 2 和一个空行。

      [ ] 是用来表示列表或者说数组的。

      ="x" 即是若 &subject 的子串即 &subject.substr(&pos, "x".length) 为 "x" 时则返回 "x" 并将 &pos 设为 &pos + "x".length 。(我用Javascript的表达式来做伪代码说明了 :P)

      显然 X, T, E 都是递归函数,procedure X() 中的 suspend 会依次返回 [T()] 和 [T(), ="+", X()] 作为结果。现在假设 line 为 "x+y" (不含空格):
      Step0: &subject 为 "x+y" ,&pos 为 1 。
      Step1: X1->T1->E1 得到 [[["x"]]] ,此时 &pos 为 2 。
      Step2: X2->T1->X1->T1->E1 得到 [[["x"]]],["+"],[[[["y"]]]] ,此时 &pos 为 4 ,结束。

      同理,"x*(y+z)" 的解析最后一个结果是 [[["x"]],["*"],[[["("],[[[["y"]]],["+"],[[[["z"]]]]],[")"]]]]
      "x+y*z" 的最后结果是 [[["x"]]],["+"],[[[["y"]],["*"],[[["z"]]]]]

      附上程序代码:

      ---
      现在我不知道是 Icon 的语法可怕还是 Perl 的语法可怕了 -_- Icon 的内置函数挺复杂的样子。另外,Icon 的一元运算符挺多,也有一些奇葩的运算符,不多看手册多做练习容易忘记,感觉有点像 APL/J/K 语言。

      • Jak Wings   2013/06/30

        Icon 的用户社区似乎少得可怜,某些功能也逐渐被其它语言所超越(例如 Python 也有 Generator),发展的形势貌似和 APL/J/K 差不多,都向专业方面发展,用户少是当然的,不过没人能说它不是门好语言,它只是功能有点少。Icon 本身不是面向对象的,于是有了 Unicon ,不过还是比较冷门。或许过早的扩展对这类专业性质的语言的发展会很有害吧。
        ---

        话说我是回来补充解答之前对 | 操作符的一些疑问的 :)
        Icon 有几大特点:
        1. 善用 &null 返回值作为错误反馈。
        2. 对应第一点,不存在 boolean 类型的数据,&null, 0, "" 都不算 FALSE 。只有成功或失效,成功时返回表达式的值,失效则返回 &fail 。
        3. 包括 if/case/when/until 等,几乎一切都是表达式,而且表达式都有返回值
        4. 表达式求值时遵循「goal-directed(成功导向)」原则,它会在「多个」返回值或修补元素中寻找能够满足条件的元素。Icon 的表达式并不是真的一次性返回多个值,而是类似迭代器那样逐个提供,这个特性只是为了成功导向原则而生的。例如 (i | j | k) = (0 | 1) 可以用来判断 i,j,k 中是否有值为 0 或 1 的,| 操作符是连接候选值的,这样我们便知道为什么 | 很像其它语言的 or 操作符了。

        之前我好奇为什么 write(1 | 2) 只会输出 1 和一个空行,procedure mywrite(i) { write(i) } 则会分别输出 1 和 2 和一个空行:

        write(1 | 2) 相当于 write(1) | write(2) ,若 write 被其它表达式包含了,则会继续发挥 | 的作用。mywrite 也一样,可是为什么 mywrite 会被执行两次,而 write 不会呢?
        原因是:依据 goal-directed 求值原则,候选的表达式仅在需要的时候才会被执行,即之前的表达式失效时。
        具体原因是:mywrite 没有 return 或 suspend 返回任何值,因此默认返回 &fail ,即失效。

        或许得修改一下 mywrite 这个函数才能更好地说明:

        ---
        最后,顺道说说 Icon 的另外一些特性:
        1. 没有块结构(block structure),{} 只是用来组合表达式的,不是界定代码域的。
        2. 无法在 procedure 定义中定义新 procedure ,包含 return 的递归函数也不是我们所想的那样简单……
        3. 可以将 function 和 procedure 作为参数传递。
        4. 可以用字符串来引用函数,不过编译的时候记得先引用那个函数免得被编译器优化掉。例如:"write"(1, 2, 3)
        5. procedure 支持多参数,例如 procedure vvar(1, 2, rest[])
        6. procedure 的参数虽然是值传递的,不过传递的时候是传递参数的指针,然后再产生临时变量的,读取值时用原来的指针,只有赋值时才复制变量内容产生新的指针,因此传递参数还是很快的。(我根据教程内容猜的,我还没看 Icon 的源代码……)
        7. 若 procedure 返回的是全局变量,或者全局变量的子集,则返回变量,而不是返回变量的值。要阻止它,可以用点操作符 . 明确地指定返回变量的值,如 return .VAR[i] 。
        8. 变量的解引用(即存取指针内容)在需要的时候便会发生,如:(if a > b then a else b) := 0 会将 0 赋给 a 和 b 中数值较大者,这个表达式相当简洁。

        Icon 还是挺有趣的,不像 APL/J/K 那样极端得让人过几周就把语法忘光了。

        • Jak Wings   2013/06/30

          啊,我说错了一点,应该是「1. 善用 &fail 作为失效反馈,表达式无法正常返回值时便自动返回 &fail ,而 &fail 会使相关表达式也一起失效。」

跳到底部
返回顶部