符号表和namespace的理解与疑惑

elisp中符号默认情况下是在一个叫做obarray的全局变量中管理的

符号的组成
1. 符号名     通过symbol-name访问
2. 符号值     通过symbol-value访问
3. 函  数     通过symbol-function访问
4. 属性列表    通过symbol-plist访问

之所以变量名和函数名可以分开在不同的namespace,原来同一个符号可以同时指定变量值和函数,互不干扰。

(setq  a 100)                    ; 100存储在符号a的符号值域
(defun a (name)                  ; 函数内容存储在符号a的函数域
  (message "Hello %s!" name))

(+ a 100)             ; 利用symbol-value提取符号a的符号值域的内容
  => 200

(a "world")           ; 利用symbol-function提取符号a的函数域的内容
  => Hello world!

;; 当a作用参数传递
(defun test (fn text)
  (message (number-to-string (symbol-value fn))) ; 提取的是符号a的符号值域的内容
  (funcall fn text))  ; 也是利用symbol-function提取符号a的函数域的内容

(test 'a "world")
  => 100
  => Hello world!

(test #'a "world")
  => 100
  => Hello world!

'a 和 #‘a的区别在于有没有compile,但有下面问题没搞清楚

(test 'a "world")
(test #'a "world")  ; a函数被compile之后的byte-code在什么地方?
(symbol-function 'a)  => 还是a函数的原始定义,上一行没有改变a函数域的内容为byte-code

(byte-compile 'a)   ; compile后,byte-code会替换掉a的函数域的内容
(symbol-function 'a) => a函数compile后的byte-code
4 个赞

并没有这个不同,可能你看错了。

#' 相比 ' 提供了更多的信息——后面是个函数,不是变量或者别的,这样 Byte Compiler、补全等能利用这一点,给出更精确的提示(应该不难想象怎么实现:补全的后端发现用户输入了 #',然后就只给补全函数名,过滤掉变量,但如果用户输入了 ',就补全函数和变量;编译器只要检查下 #' 的函数有没有定义就 OK 了,没定义就发个警告,但如果是 ' 可能就没办法了,编译器有时不能确定这里应不应该是个函数)

就是说,以后写代码,若是引用函数时,#'这种写法比较规范,便于Byte Compiler、补全等功能,也就是更友好,和compile处理没直接关系。

嗯,#'foo'foo 如果直接执行的话,是没区别的。不执行的话,(当然)就不同了:

'(1 #'foo 'bar)
;; => (1 (function foo) (quote bar))
1 个赞

funcallapply 中,即便用 ',Byte Compiler 也会检查函数有没有定义。

(funcall 'foo)

下面的代码,'和`是一样的效果,它们在宏定义里的表现又不一样,那么有那些异同点呢?

'(1 #'foo 'bar)
;; => (1 (function foo) (quote bar))

`(1 #'foo 'bar)
;; => (1 (function foo) (quote bar))

这个我明白,因为funcall已经明确了是函数调用。

嗯,应该是 Byte Compiler hard-code 了 funcall,比如 mapcar 就不会有警告:

(mapcar 'foo ())

所以要用 #' 来提示 Byte Compiler

(mapcar #'foo ())
(let ((a ''(1 #'foo 'bar))
      (b '`(1 #'foo 'bar)))
  (equal (macroexpand a) (macroexpand b))) ;; => t

`backquote 是个 Macro,配合 , 选择性地执行列表的部分元素;'quote 是个 Special Form(类似函数),用来直接返回(未被执行过的)参数。

这个我收藏了,以前一直没区分开

''(1 #'foo 'bar)
'`(1 #'foo 'bar)

还有这种写法,学习了

感觉用户能自己实现 ' / quote` / backquote,实现下或许有助于理解。

quote ?

(defmacro my-quote (arg)
  `(read ,(prin1-to-string arg)))

backquote 完全是用 Lisp 实现的。

不好意思,还有要确认的地方。

(defmacro my-quote (arg)
  `(read ,(prin1-to-string arg)))

(my-quote (car '(2 3 4)))
  => (car (quote (2 3 4)))  ; 参数(car '(2 3 4))被转换成quote后返回

(prin1-to-string (car '(2 3 4)))
  => "2"                    ; 参数(car '(2 3 4))的结果给了prin1-to-string

`(read ,(prin1-to-string arg)) 这句,只是让prin1-to-string执行,而arg保留原形式。

拓展一下,若想在这里让arg也执行的话,如何处理?

`(read ,(prin1-to-string ,arg)) 刚试了一下,这样的写法不对,,(prin1-to-string arg) 前的逗号只对第一个元素起作用?

(my-quote (+ 1 2))

,(prin1-to-string arg)会让lisp解释器对这个括号内的语句求值。prin1-to-string是个函数,会对它的参数arg求值。arg的求值结果是(+ 1 2)这个表本身。因为macro是不对传入他的参数求值的。

(defun my-quote (arg)
  (read (prin1-to-string arg))

改成函数的话。lisp解释器执行函数式会先对arg求值。得到结果3。然后传入prin1-to-string

好的,知道了。宏对参数的求值,只是其自身,若想强制让这个参数执行,是不是得在宏里加上eval?

#''还是有点区别的。

CL-USER> (flet ((a (x) (+ 1 x))) (apply #'a '(2)))
3 (2 bits, #x3, #o3, #b11)
CL-USER> (flet ((a (x) (+ 1 x))) (apply 'a '(2)))
; Debugger entered on #<UNDEFINED-FUNCTION A {1001D46473}>
[1] CL-USER> 
; Evaluation aborted on #<UNDEFINED-FUNCTION A {1001D46473}>

这里用'会导致找不到函数。

ELISP> (macroexpand-all '(cl-flet ((a (x) (1+ x)))
                           (apply #'a '(3))
                           (apply 'a '(3))))
(let*
    ((--cl-a--
      #'(lambda
          (x)
          (1+ x))))
  (progn
    (apply --cl-a--
           '(3))
    (apply 'a
           '(3))))

可以看出区别

flet is a Lisp macro in ‘cl.el’.

This macro is obsolete since 24.3; use either cl-flet' or cl-letf’.

Make temporary overriding function definitions. This is an analogue of a dynamically scoped ‘let’ that operates on the function cell of FUNCs rather than their value cell. If you want the Common-Lisp style of ‘flet’, you should use ‘cl-flet’. The FORMs are evaluated with the specified function definitions in place, then the definitions are undone (the FUNCs go back to their previous definitions, or lack thereof).

这是为了和 Common Lisp 标准看齐。Common Lisp 的 flet 是 Special Op

A special operator has access to the current lexical environmentand the current dynamic environment.

你想说的是用 ' 找不到函数。flet 没这个区别,两种写法均可。注意 Emacs 的 fletcl-flet 不完全等价。之前没想过这个,的确值得一提,虽然用户(比如刚刚提到的 cl-flet)也可以自行决定怎么处理,比如完全可以写一个 my-apply,仅支持 #',不允许 '

(defmacro my-apply (fun &rest args)
  "Like `apply', but FUN must be quoted with #'."
  (pcase fun
    (`#',f `(apply #',f ,@args))
    (_ (user-error "%s must be quoted with #'" fun))))