目录
前情提要
我有一个维护了很久的 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))
这里是一个参考宏的范例,你有没有发现它和函数长得非常像?这就对了,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
语句。
在宏的帮助下,我们能自如的实现很多原本写起来很麻烦的语法,就仿佛可以自定义语法一样,简直是如有神助啊🥰
有点晚了,先睡觉了,之后继续写 感谢冬夜夜的催稿,这个博客我总是没动力写,感觉没有他我估计都懒得写了,虽然我写的东西其实挺无聊的(大雾