目录
前情提要
我有一个维护了很久的 neovim 配置,大概有近 600 次 commit,我基本上没事闲着都在折腾这坨 lua 代码,使我几乎成了一个超级熟练的 lua 选手。
但是我在有一天 突然遇见了一个神秘的 fennel语言, 是一种 lua 的 lisp 方言。
这让我不禁想起了我仍在计划中的至今没能看完几面的 SICP 😭
于是我决定,我要把我的 neovim 配置全部改成 fennel的格式😈。说真的,这个决定非常的蠢,fennel的生态相比 lua 还是差了太多太多,不过总的来说我玩的非常开心,爱来自 LISP 宏!
Fennel介绍
作为一门 lisp方言,语法当然非常简单,最基本的前缀表达式我就不多说了,简单理一下它的特殊语法:
-
定义函数:
(fn foo [a b c] ;; 定义函数 返回 a + b 的和 (+ a b)) #(+ a b) ;; 相当于匿名函数 -
定义表:
(local list {:foo {:bar 14514}}) ;; 定义一个表,这是lua的核心语法等价于:
local list = { foo = { bar = 14514 } } -
函数调用:
(foo d e f) ;; 调用 foo 函数 以 d e f 为实参
最主要的就是这三种语法,其中 :foo 是 "foo" 的特殊写法,用起来着实挺方便的。
剩下的基本上就是 lua 的部分了,基本上 lua 能用的工具函数它都能用,我就不展开讲了,默认大家都会 lua 😤
优势
因为这其实相当于我第一门认真学的 lisp 语言,所以我还真说不上来什么优势,但是 lisp 的优势,这门语言肯定都是有的,lua的一部分优势(便于移植)也能够享受到。
lisp 由于语言本身就是S-表达式,所以最重要的特性,也就是 代码即数据,你可以结构化的操作代码,就仿佛在操作数据一样,这就是宏。
有趣的地方
最有趣的地方,当然就是宏了。笔者第一次接触宏是在 C/C++ 里,那时候我对宏的认识仅限于字符替换。虽然宏本质就是替换,但是lisp宏的功能远超常人想象的强大。
(macro thrice-if [condition result]
(fn step [i]
(if (< 0 i)
`(if ,condition (do ,result ,(step (- i 1))))))
(step 3))
尝试试用一下这个宏:
(thrice-if true (print "Hello, world!"))
结果是:
"Hello, world!"
"Hello, world!"
"Hello, world!"
这里是一个参考宏的范例,你有没有发现它和函数长得非常像?这就对了,lisp 宏最厉害的地方在于你写它和函数几乎没区别!本质上函数是操作数据的,但是 lisp 代码的本质不就是 (a b c d e) 嘛!你就可以很自然的直接拆分成 token 处理。
这里我详细解释一下这个宏的含意:
`(...)我叫它模板块,代表这个括号内的内容都是最后直接生成到结果里的,相当于无论这里面写了什么,都会直接生成到最后的结果里,不会被作为代码执行。,xxx代表在模板块中的特定插值,而被插值的变量也好,函数也好,都会作为真正代码被执行,而不再只是类似字符串的替换。
这就像字符串格式化一样,printf("abc%d", c);,这里 abc 就是模板块,%d 对应的 c 就是插值。
有了前置之后你大概应该就能明白这里面本质上是一个递归函数 (step n),它在这里递归了三层,每次都会再上一次产生的模板块内递归调用 (step (- n 1)),然后再一次生成对应的模板块,直到递归结束。
这样我们就能很容易理解最后得到的代码:
(if true
(do
(print "Hello, world!")
(if true
(do
(print "Hello, world!")
(if true
(do
(print "Hello, world!")))))))
很清晰,这个宏生成了一个三层嵌套的 if 语句。
在宏的帮助下,我们能自如的实现很多原本写起来很麻烦的语法,就仿佛可以自定义语法一样,简直是如有神助啊🥰
有点晚了,先睡觉了,之后继续写 感谢冬夜夜的催稿,这个博客我总是没动力写,感觉没有他我估计都懒得写了,虽然我写的东西其实挺无聊的(大雾