其实你已经在使用Lisp语法了 lispunix
程序员文章站
2022-07-16 13:38:00
...
UNIX开发人员(以下简称UD, Unix Developer):我再也不会碰LISP了。太可怕了!
我:为什么这么说?
UD:它的语法!那个波兰式的前缀语法看得眼睛都花了,也就只有它在用了。你看看这些个括号!
我:好吧,但很多人认为这个可读性很强,尽管他们也承认是得花点时间才能习惯它。但我觉得你错了。很多人其实每天都在使用Lisp语法。。。
UD:据我所知,没人像你说的这样。
我:。。他们可能自己都没意识到这个。事实上,我认为你也在使用它。
UD:等等,你说什么?!
我:你用的这个特殊的Lisp语法的变种又叫做Bourne Shell。
UD:这我可听不明白了。shell和Lisp有毛关系?
我:你看下shell里面,你先输入程序名,然后是参数,它们用空格为分隔开。Lisp里面也是这样的,只不过你放了一个左括号在前面,最后又加上了一个右括号。
Shell: run-something arg1 arg2 arg3
Lisp: (run-something arg1 arg2 arg3)
UD: 我还是没感觉有什么像的。
我:现在你需要一种机制将表达式组合起来——也就是将一个表达式的输出作为另一个表达式的输入。在Lisp里面,你需要嵌套列表了。那么在shell里呢?
UD:`
我:对的。或者是$(),它的好处是更容易嵌套了。我们来试一下算术运算。你在shell里是怎么进行数学运算的?
UD: expr。或者shell内建的let,比如这样:
我:这个可能有点不太符合UNIX的精神了——一个程序应当只做一件事情——我们应该有一个程序来做加法,一个做减法,还有的分别做乘法和除法。
用C来写一个的话很简单:
这个程序是根据名字的最后一个字符进行分发的,因此你可以将它编译成+,-, x和d(这里乘法和除法用的名字不太常用,因为这是合法字符也省得转义了)
现在看吧:
UD: 好吧,这真的看真来很像Lisp了。
我:是的,但这就是shell。我们的两个基本原则——程序名在前,$()用来组合操作——这样就能明确区分出求值的顺序,也不需要做额外的解析了,因为shell已经提供了这样的功能。
UD:那么shell也是Lisp的一种吗?
我:不算是。shell是字符串类型的:程序接收文本参数,输出的也是文本的结果。要想成为Lisp中的一员,它还得有一个组合类型:列表或者cons单元,你可以用它来构建列表。然后,你还需要能够用数据结构来表示代码,可以编写程序来对代码进行转化。
不过,shell的语法中蕴含着Lisp之道。
我知道我这里漏掉了许多细节,比如shell的重定向,命令替换,子进程,程序除了命令行参数外还有标准准入,以及管道,等等——这些都使得shell看起来不那么像Lisp。不过我认为这是向大家介绍Lisp语法的一个很有趣的方式。
原创文章转载请注明出处:http://it.deepinmind.com
英文原文链接
我:为什么这么说?
UD:它的语法!那个波兰式的前缀语法看得眼睛都花了,也就只有它在用了。你看看这些个括号!
我:好吧,但很多人认为这个可读性很强,尽管他们也承认是得花点时间才能习惯它。但我觉得你错了。很多人其实每天都在使用Lisp语法。。。
UD:据我所知,没人像你说的这样。
我:。。他们可能自己都没意识到这个。事实上,我认为你也在使用它。
UD:等等,你说什么?!
我:你用的这个特殊的Lisp语法的变种又叫做Bourne Shell。
UD:这我可听不明白了。shell和Lisp有毛关系?
我:你看下shell里面,你先输入程序名,然后是参数,它们用空格为分隔开。Lisp里面也是这样的,只不过你放了一个左括号在前面,最后又加上了一个右括号。
Shell: run-something arg1 arg2 arg3
Lisp: (run-something arg1 arg2 arg3)
UD: 我还是没感觉有什么像的。
我:现在你需要一种机制将表达式组合起来——也就是将一个表达式的输出作为另一个表达式的输入。在Lisp里面,你需要嵌套列表了。那么在shell里呢?
UD:`
我:对的。或者是$(),它的好处是更容易嵌套了。我们来试一下算术运算。你在shell里是怎么进行数学运算的?
UD: expr。或者shell内建的let,比如这样:
$ let x='2*((10+4)/7)'; echo $x 4
我:这个可能有点不太符合UNIX的精神了——一个程序应当只做一件事情——我们应该有一个程序来做加法,一个做减法,还有的分别做乘法和除法。
用C来写一个的话很简单:
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char **argv) { int mode = -1, cnt = argc - 1, val, i; char **args = argv + 1; switch (argv[0][strlen(argv[0]) - 1]) { case '+': mode = 0; break; case '-': mode = 1; break; case 'x': mode = 2; break; case 'd': mode = 3; break; } if (mode == -1) { fprintf(stderr, "invalid math operation\n"); return 1; } if ((mode == 1 || mode == 3) && !cnt) { fprintf(stderr, "%s requires at least one arg\n", argv[0]); return 1; } switch (mode) { case 0: val = 0; break; case 2: val = 1; break; default: val = atoi(*args++); cnt--; break; } while (cnt--) { switch (mode) { case 0: val += atoi(*args++); break; case 1: val -= atoi(*args++); break; case 2: val *= atoi(*args++); break; case 3: val /= atoi(*args++); break; } } printf("%d\n", val); return 0; }
这个程序是根据名字的最后一个字符进行分发的,因此你可以将它编译成+,-, x和d(这里乘法和除法用的名字不太常用,因为这是合法字符也省得转义了)
现在看吧:
$ x 2 $(d $(+ 10 4) 7) 4
UD: 好吧,这真的看真来很像Lisp了。
我:是的,但这就是shell。我们的两个基本原则——程序名在前,$()用来组合操作——这样就能明确区分出求值的顺序,也不需要做额外的解析了,因为shell已经提供了这样的功能。
UD:那么shell也是Lisp的一种吗?
我:不算是。shell是字符串类型的:程序接收文本参数,输出的也是文本的结果。要想成为Lisp中的一员,它还得有一个组合类型:列表或者cons单元,你可以用它来构建列表。然后,你还需要能够用数据结构来表示代码,可以编写程序来对代码进行转化。
不过,shell的语法中蕴含着Lisp之道。
我知道我这里漏掉了许多细节,比如shell的重定向,命令替换,子进程,程序除了命令行参数外还有标准准入,以及管道,等等——这些都使得shell看起来不那么像Lisp。不过我认为这是向大家介绍Lisp语法的一个很有趣的方式。
原创文章转载请注明出处:http://it.deepinmind.com
英文原文链接
上一篇: (转)Lisp的本质
下一篇: ruby 正则匹配非站内链接