letrec 和 let 的区别?

请问 letreclet 的区别是什么?不太了解 common lisp,这个话题搜到的结果大多数和 elisp 没有关系。

看代码注释说只在 lexical-binding 下有作用,不太明白这个到底是用来做什么的。

NEWS.24 有提到:

New macro letrec to define recursive local functions.

比如:

(setq lexical-binding t)
     => t

(letrec ((len (lambda (l)
                "Return the length of list L."
                (if (null l)
                    0
                  (1+ (funcall len (cdr l)))))))
  (list
   (funcall len '())
   (funcall len '(1))
   (funcall len '(1 2 3))))
     => (0 1 3)
2 个赞

原来 recrecursive,学习了

http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_132

Semantics: The s are bound to fresh locations holding undefined values, the s are evaluated in the resulting environment (in some unspecified order), each is assigned to the result of the corresponding , the is evaluated in the resulting environment, and the value(s) of the last expression in is(are) returned. Each binding of a has the entire letrec expression as its region, making it possible to define mutually recursive procedures.

这段引用里面的<...>都被md吃掉了,点进去看吧。

这段说明和 emacs 中 letrec 的函数文档比较接近,之前看不太明白。现在稍微明白了点。

貌似 let 的变体不下几十种,这是函数式编程语言的特点吗?

https://docs.racket-lang.org/reference/let.html#(form._((lib._racket/private/letstx-scheme..rkt)._let))

letrec 其实是 Scheme 风格的东西,Common Lisp 因为函数和变量命名空间不一样,用的是 letflet (不可做递归引用),以及 let* labels (可以使用递归),还有 macrolet, letf letf* (作用和 setf 类似)。

Emacs Lisp 本身也可以模拟 Scheme

1 个赞

写 elisp 包的时候发现 letrec 真是相当的好用啊

cl-labels 可以少写 lambda 和 funcall 啊

;###autoload
(defmacro exclamation (&rest structure)
  "Expand the $ structure as inline macros at compiling time
 to optimze byte-code."
  (cl-labels ((process
               (s)
               (if (listp s)
                   (cl-loop for i in s
                            collect
                            (if (and
                                 (listp i)
                                 (eq
                                  (car i)
                                  exclamation-macro-mark))
                                (let* ((lst (cdr i))
                                       (war (check lst)))
                                  (if war lst
                                    (eval lst)))
                              (process i)))
                 s))
              (check
               (sexp)
               ;; A example of using `catch' and `throw'.
               (catch 'unbound
                 (cl-loop for x in sexp do ; do form in `loop' always returns nil.
                          (if (listp x)
                              (let ((head (car x)))
                                (unless (fboundp head)
                                  (throw 'unbound t)
                                  (warn
                                   "symbol's function definition void: `%S' "
                                   (car x)))
                                (unless (or
                                         (macrop head)
                                         (special-form-p head))
                                  check x))
                            (or
                             (not (symbolp x))
                             (boundp x)
                             (fboundp x)
                             (progn
                               (throw 'unbound t)
                               (warn
                                "symbol's definition void: `%S'"
                                x))))))))
    (cons 'progn (process structure))))
1 个赞

但是这种情况下不如 letrec 好记,:joy: ,看到 let 开头就知道它的大概用法应该是怎样的,因为基本上包含 let 的都和 binding 有关。

你要是不说我绝对想不到 cl-labels 是干吗的。谢谢了,又学到了。

label 其实是 Lisp 7 个原始操作符 2 个记号中 2 个记号之一(另外一个就是 lambda

label 具体定义以及其他 7 个操作符的具体介绍可以参照这篇文章: http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#tex2html3

简单来说 (defun func (args ...) body) 等价于 (label func (lambda (args ...) body))

这样一来为什么定义多个临时函数的特殊形式叫 labels 就好理解了了。

另外在现代的 Lisp 实现中 label 已经消失了。Emacs Lisp 用 C 实现了 defalias,然后用 defalias 定义了 defmacro,用 defmacro 定义了 defun。而早期 Lisp 是没有宏的。其他 Lisp 也用各自的方法定义 defun

4 个赞