Skip to content

在 neovim 中使用 lisp 的奇幻之旅

Updated: at 04:21

目录

前情提要

我有一个维护了很久的 neovim 配置,大概有近 600 次 commit,我基本上没事闲着都在折腾这坨 lua 代码,使我几乎成了一个超级熟练的 lua 选手。

但是我在有一天 突然遇见了一个神秘的 fennel语言, 是一种 lualisp 方言。 这让我不禁想起了我仍在计划中的至今没能看完几面的 SICP 😭

于是我决定,我要把我的 neovim 配置全部改成 fennel的格式😈。说真的,这个决定非常的蠢,fennel的生态相比 lua 还是差了太多太多,不过总的来说我玩的非常开心,爱来自 LISP 宏!

Fennel介绍

作为一门 lisp方言,语法当然非常简单,最基本的前缀表达式我就不多说了,简单理一下它的特殊语法:

最主要的就是这三种语法,其中 :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 处理。

这里我详细解释一下这个宏的含意:

这就像字符串格式化一样,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 语句。 在宏的帮助下,我们能自如的实现很多原本写起来很麻烦的语法,就仿佛可以自定义语法一样,简直是如有神助啊🥰

有点晚了,先睡觉了,之后继续写 感谢冬夜夜的催稿,这个博客我总是没动力写,感觉没有他我估计都懒得写了,虽然我写的东西其实挺无聊的(大雾

有用的工具

结语