一个更优雅的 lisp 语法设计

2018-11-11

lisp 缺一个好的语法

满世界的人都对全是括号的表示充满讨厌,我也讨厌。有人说,lisp 有语法么? lisp 的标准语法不是 S表达式么。极端分子甚至强调 S表达式就是世界上最好的语法,括号是它精华的东西,认为搞些其它花里胡哨的语法偏离了教义。

先打脸

(defun map
    F []		-> []
    F [X | Xs]	-> [(F X) | (map F Xs)])
    
这种写法不比下面这种优雅得多? 不要拿模式匹配说事,模式匹配的问题后面讲。
(define map
    (lambda (f l)
        (if (null? l)
            '()
            (cons (f (car l))
                  (map f (cdr l))))))

S表达式里面,只有两种东西,一种是 symbol,一种是 cons。然而实际上语言都会支持各种原子类型的符号,比如 number,比如字符串。

遇到自定义类型的时候,就有一点欠缺了,定义结构体什么的,纯用 S表达式就有点别扭。 为什么不用 #R"(?<=95|98|NT|2000)Windows" 来表示正则? 或者是自定义用来表示结构体的语法 T{A : 3, B : "sd"}。一旦这些东西多起来之后,S表达式就不那么纯粹了。

reader 宏很有意义,自定义 reader 收获更大

其实 lisp 是有 reader 宏的,像单引号的 quote,反引号的 quasiquote,逗号的 unquote,这些都是内置的 reader 宏。reader 在遇到任何这些特殊符号的时候,就自动改写S表达式了。(quote s) 的等价写法 's

reader 宏是很有意义的,比如我们遇到 { 就按我们自己定义的方式来解析,最后输出的还是 S表达式。既然能够接受 reader 宏,为什么不更推进一步,自定义整个 reader 呢?

自定义 reader 之后,语法喜欢什么口味都可以自己设计,比如即使变成这样子,都是完全合理的,反正过了 reader 之后,就是正统的 S表达式了:

func f() {
}
不用关心真正的语法,可以用任何自己喜欢的表示,自己写 reader,然后转换成 S表达式。

quote 的问题

quote 是一个 reader 宏。在知乎回答问题的时候,提到过 quote。

quote 存在的真正意义就是,让用户可以选择到底是代码还是数据,但它只是实现的途径之一,但并不是必须的。

在 shen 语言里面,一个符号求值后得到的是它自身,不需要使用 quote。为了区分变量和符号,求值规则就有点特殊了,要取决于上下文,有绑定就是变量,没有绑定就是符号:

(lambda (x) x y) 在这个 lambda 里面, x 是变量, y 是符号

即使不使用 shen 这么特殊的规则,要生成符号也完全可以使用其它方式,比如 (intern "x")。只要有办法制造符号,制造 cons,就可以制造 S表达式。任何语言,只要写一个 reader,制造 S表达式还不是很简单的事情? 那岂不是所有语言都可以是 lisp 了? 那我们 lisp 党的迷之优越岂不是荡然无存了?

扯了半天废话,我是要说什么来着,哦, quote 没毛卵用,并不是 lisp 里面必须的。

嵌套反引用有害

直接帖个链接,不解释了。

为了解决嵌套反引用的问题,我提出(其实不是我提出的,shen 语言是这么干的)一个更好的语法表示:中括号的表示不求值,圆括号表示需要求值。

[A (f B [C D])]

不论嵌套多少层,仍然是一目了然。

pattern match 的 pattern 语法

不管是 syntax-rules,还是 pmatch 里面,pattern 的写法都让人感到很不一致,像是不在同一门语言里面。 举一个例子:

(pmatch exp
      [,x (guard (symbol? x)) x]
      [(,M ,N) `(,(compile-bc M) ,(compile-bc N))]
      [(lambda (,x) ,y) (guard (eq? x y)) `I]
      [(lambda (,x) (,M ,y))
      (guard (eq? x y) (not (occur-free? x M))) (compile-bc M)]
      [(lambda (,x) (,M ,N)) (guard (and (not (occur-free? x M)) (occur-free? x N)))
      `((B ,(compile-bc M)) ,(compile-bc `(lambda (,x) ,N)))]
      [(lambda (,x) (,M ,N)) (guard (and (occur-free? x M) (not (occur-free? x N))))
      `((C ,(compile-bc `(lambda (,x) ,M))) ,(compile-bc N))]
      [(lambda (,x) (,M ,N)) (guard (or (occur-free? x M) (occur-free? x N)))
      `((S ,(compile-bc `(lambda (,x) ,M))) ,(compile-bc `(lambda (,x) ,N)))]
      [(lambda (,x) ,M) (guard (not (occur-free? x M))) `(K ,(compile-bc M))]
      [(lambda (,x) ,M) (guard (occur-free? x M))
      (compile-bc `(lambda (,x) ,(compile-bc M)))]**

注意观察到,匹配符号用的符号本身,匹配变量就需要加一个逗号。 我认为不是很好,最符合直觉的方式,应该让 pattern 匹配的对象,跟构造这个对象使用的同一种语法。比如:

  • pattern 是一个数字 42,那它应该匹配到的也是 42;
  • pattern 是一个字符串 "xyz",那它匹配的也是一个字符串 "xyz";
  • 构造一个 symbol 的方式是 (quote s),缩写是 's,那以匹配符号 s 的 pattern 也应该是 's;
  • pattern 是 (cons X Y),它用于构造一个 cons,那么匹配的也应该是一个 cons;
  • x 就直接匹配任何变量了

还记得前面说过自定义 reader,在自定义的 reader 里面,构造 list 的方式是使用中括号 []。然后

[a b c]
经过 reader 转换成 S表达式以后,变成
(cons a (cons b (cons c nil)))
注意到没,pattern 匹配的对象,跟构造对象使用的是同种语法表示!整个语言就更一致了。

构造一个 symbol 是使用 (quote s),那么模式匹配中也使用 (quote s)。 假设我们自定义结构体的语法,比如 T{A, B},那这个东西当 pattern 表示时,也应该匹配结构体 T 对象。

最后看一个,(quote (a b c)) 的含义是啥? 它不是一个构造链表的函数,不应该使用这种东西放在模式匹配里面。 即使没有 quote,假设构造符号使用的是 (intern "xxx"),那模式匹配的写法也应该是 (intern "xxx")

为了避免 (quote (a b c)) 这种奇怪的东西出现在语言里面,最好的办法是,语言里面只允许使用 (quote symbol),不能 quote 其它的。

好啦,看个例子:

(match v
    x		-> (+ x 2)	;; 模式匹配一个变量
    []		-> #t	;; #t 和 #f 和 [] 都是基本对象
    [a b]	-> a	;; [a b] 其实是 (cons a (cons b []))
    [a | b]	-> a	;; [a | b] 等价于 (cons a b)
    T{a}	-> a	;; 如果有自定义结构体
    'a		-> 'b	;; 匹配一个符号, 'a 等价于 (quote a)
    _		-> (error "nothing")) ;; 下划线跟变量差不多

定义函数也是使用模式匹配的,跟 match 一样:

(defun f
	a b -> 42	;; 跟 match 不同的是这里可以多参数
    ['lambda x] y -> y)
    
跟 shen 语言不同的是,不使用大小写区分变量和符号,符号使用 quote
schemelispcora