elisp为什么能在let里定义递归函数?

这里的行为相当怪异, let起到了类似labels的作用.

感觉是elisp对let做了什么hack处理.

(defun my-fact (input)
  "Return factorial of INPUT."
  (let ((my-inside-fact #'(lambda (to-multiply next-gen)
							(if (< to-multiply 1)
								next-gen
							  (funcall my-inside-fact (- to-multiply 1) (* next-gen to-multiply))))))
	(funcall my-inside-fact (- input 1) input)))

(message "%s" (my-fact 10))
;; => 3628800

这个行为很正常啊,my-inside-fact是一个变量而不是函数(elisp里函数和变量在不同的空间里),这个变量的值是一个函数(准确地说是一个 lambda closure)。你调用这个变量里的函数的时候也是用的funcall而不是apply。你看一下funcallapply的区别就知道了。

怪异的是可以定义递归函数,

在cl上类似的实现就会直接抛函数未定义.

没有任何问题,cl的labels定义的是函数(至少cl-labels是这样),不是变量。你把我之前的回复再读一遍。

因为 Emacs Lisp 默认是动态作用域,Common Lisp 是靜态作用域。

在 elisp 里开 (setq lexical-binding t) 就会和 CL 一样报错

Debugger entered--Lisp error: (void-variable my-inside-fact)
  (funcall my-inside-fact (- to-multiply 1) (* next-gen to-multiply))
  (if (< to-multiply 1) next-gen (funcall my-inside-fact (- to-multiply 1) (* next-gen to-multiply)))

在 CL 里 (defvar my-inside-fact) 以后 (my-fact 10) 能和 elisp 一样不报错运行

CL-USER> (defvar my-inside-fact)
MY-INSIDE-FACT
CL-USER> (defun my-fact (input)
  "Return factorial of INPUT."
  (let ((my-inside-fact #'(lambda (to-multiply next-gen)
                            (if (< to-multiply 1)
                                next-gen
                              (funcall my-inside-fact (- to-multiply 1) (* next-gen to-multiply))))))
    (funcall my-inside-fact (- input 1) input)))
MY-FACT
CL-USER> (my-fact 10)
3628800 (22 bits, #x375F00)
2 个赞

用let定义递归函数得用letrec或者cl-labels

(defun my-fact (input)
  "Return factorial of INPUT."
  (letrec ((my-inside-fact
            #'(lambda (to-multiply next-gen)
                (if (< to-multiply 1)
                    next-gen
                  (funcall my-inside-fact (- to-multiply 1) (* next-gen to-multiply))))))
    (funcall my-inside-fact (- input 1) input)))

;; Expands to 

(defun my-fact (input)
  "Return factorial of INPUT."
  (let
      (my-inside-fact)
    (setq my-inside-fact
          (function
           (lambda
               (to-multiply next-gen)
            (if
                (< to-multiply 1)
                next-gen
              (funcall my-inside-fact
                       (- to-multiply 1)
                       (* next-gen to-multiply))))))
    (funcall my-inside-fact
             (- input 1)
             input)))
2 个赞