一個更優雅的 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 巨集。在ofollow,noindex" target="_blank">知乎回答問題 的時候,提到過 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