Lisp 匿名递归函数 v2:在 Common Lisp 中实现 Clojure 的 fn

简介: 我在之前的文章《Lisp 匿名递归函数》中提及 Emacs Lisp、Scheme 和 Common Lisp 中默认都没提供定义可递归的 lambda 函数的方法。并在文章里提供了我自己实现的 Emacs Lisp 版本和 Common Lisp 版本。在那之后,我学习了 Clojure,发现 Clojure 中的 fn 在定义 lambda 函数的同时还允许给它取一个临时的名字,这样就能

我在之前的文章《Lisp 匿名递归函数》中提及 Emacs Lisp、Scheme 和 Common Lisp 中默认都没提供定义可递归的 lambda 函数的方法。并在文章里提供了我自己实现的 Emacs Lisp 版本和 Common Lisp 版本。

在那之后,我学习了 Clojure,发现 Clojure 中的 fn 在定义 lambda 函数的同时还允许给它取一个临时的名字,这样就能在函数体中递归地调用自己了,比如下面用来临时求第12个斐波那契数的匿名函数:

((fn fibonacci [n]
   (if (<= 2 n)
     (+ (fibonacci (- n 1))
        (fibonacci (- n 2)))
     1))
 12)

这个方法比我之前实现的要高明的多!我的方法会额外“霸占”一个名字“this”来代表自己,这样很容易有命名冲突的问题。但像 fn 这样,名字由开发者自己提供,就能避免这样的问题。因此,我开始琢磨怎么把 fn 迁移到 Common Lisp 中。

有了之前开发的经验,这一次实现起来顺手多了。观察 fn 的语法,与 lambda 相比它多了一个可选的名字。所以,当函数名未提供时和 lambda 无区别:

(defmacro fn (&rest body)
  (if (listp (car body))
    `(lambda ,@body)
    ))

然后是 else 块,这部分和之前文章里介绍的一样,都是需要让返回的匿名函数能识别一个额外的函数名,并且那个函数名指向函数本身。区别仅是之前 hard code 成 this,而这回名字由开发者指定。所以,也是用“局部函数”来实现,即 Common Lisp 中的 labels,或 Emacs Lisp 中的 flet:

(defmacro fn (&rest body)
  (if (listp (car body))
    `(lambda ,@body)
    `(lambda (&rest args)
       (labels (,body)
         (apply (function ,(car body)) args)))))

来写个函数测试一下。比如输出一棵树的所有子节点:

(funcall
  (fn dump-list (o)
    (if (consp o)
      (dolist (item o)
        (dump-list item))
      (format t "~A~%" o)))
  '(1 (2 (3 (4) 5) 6) 7 (8 9)))

在 clisp 中执行结果如下:

λ clisp -q
[1]> (defmacro fn (&rest body)
  (if (listp (car body))
    `(lambda ,@body)
    `(lambda (&rest args)
       (labels (,body)
         (apply (function ,(car body)) args)))))
FN
[2]> (funcall
  (fn dump-list (o)
    (if (consp o)
      (dolist (item o)
        (dump-list item))
      (format t "~A~%" o)))
  '(1 (2 (3 (4) 5) 6) 7 (8 9)))
1
2
3
4
5
6
7
8
9
NIL
[3]>

目录
相关文章
|
7月前
|
Rust 区块链
学Rust不学Cargo,等于没学Rust:features特性详解
在 Rust 中,Cargo 的 "features" 是一种条件编译机制,允许在编译 crate 时编译部分代码。这样可以在一个 crate 中提供多个功能,并根据需要选择性地启用或禁用这些功能。
210 1
|
7月前
|
Rust
Rust中的Fn、FnMut 和 FnOnce都有什么区别?
在 Rust 中,`Fn`、`FnMut` 和 `FnOnce` 是三个用于表示闭包类型的 trait。闭包是一种可以捕获其环境变量的函数。在创建闭包是会默认实现这几个 trait 中的一个。
150 4
|
安全 Go PHP
Golang 语言的编程技巧之变量
Golang 语言的编程技巧之变量
35 0
|
7月前
|
Rust 安全 算法
【深入探索Rust:结构体、枚举与模式匹配】A Deep Dive into Rust: Structs, Enums, and Pattern Matching
【深入探索Rust:结构体、枚举与模式匹配】A Deep Dive into Rust: Structs, Enums, and Pattern Matching
99 0
【深入探索Rust:结构体、枚举与模式匹配】A Deep Dive into Rust: Structs, Enums, and Pattern Matching
|
7月前
|
存储 Swift
Swift 语言:什么是闭包(Closure)?它们与函数的区别是什么?
Swift 语言:什么是闭包(Closure)?它们与函数的区别是什么?
139 1
|
安全 Go
Golang 语言中的内置函数 make 和 new
Golang 语言中的内置函数 make 和 new
35 0
|
Rust 编译器 Shell
Rust学习笔记之包、Crate和模块
1. Rust中包和 crate 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️ 2. 模块控制作用域与私有性 推荐阅读指数 ⭐️⭐️⭐️⭐️ 3. 路径用于引用模块树中的项 推荐阅读指数 ⭐️⭐️⭐️⭐️ 4. use 将名称引入作用域 推荐阅读指数 ⭐️⭐️⭐️⭐️ 5. 将模块分割进不同文件 推荐阅读指数 ⭐️⭐️⭐️
168 0
Rust学习笔记之包、Crate和模块
|
JavaScript 前端开发 Java
深入理解 Go 语言的一等函数及其应用(上)
任何一门编程语言都离不开函数,无论是命令式语言 C、多范式编程语言 C++,还是面向对象编程语言 Java、Ruby,动态语言 Python、JavaScript,函数这一语法元素都是当仁不让的核心。
|
JavaScript 前端开发 Java
深入理解 Go 语言的一等函数及其应用(中)
任何一门编程语言都离不开函数,无论是命令式语言 C、多范式编程语言 C++,还是面向对象编程语言 Java、Ruby,动态语言 Python、JavaScript,函数这一语法元素都是当仁不让的核心。