复习 Lisp: Syntax and Semantics

和 Common Lisp 稍有不同,在 Emacs Lisp 中,lambda 表达式和 function 返回的也是函数名。利用 funcall 自动转化符号为对应的函数定义的特性使得两种语言表现出的行为几乎一致,但是实质是有很大区别的。

比如

(setq foo (list #'bar #'baz)) ;; `bar' `baz' 均为合法函数定义,且 `bar' 是无参数的函数。

对这样产生的列表,进行

(funcall (car foo))

在 Emacs Lisp 中,因为被装入列表的是函数的名称,所以当 bar 的函数定义被改变后,以上的结果也会对应改变。而在 Common Lisp 中,因为是函数的「定义」被装入列表,所以当 bar 的函数定义被修改后,以上代码的结果会不变。

1 个赞

(symbolp (lambda () “”)) => nil

lambda 返回的似乎就是一个 第一个元素是 lambda 的列表…

我感觉这句话是不是这么一个意思: lisp 解释器专门添加了一条规则来, 让lambda表达式有函数名的行为, 但其本质是一个列表?

确实 common-lisp 和 elisp 在这方面, 还是有一些区别

Welcome to GNU CLISP 2.49.60 (2017-06-25) <http://clisp.org/>

Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2010

Type :h and hit Enter for context help.

[1]> (defun test () "f")
TEST
[2]> (setq a (list #'test))
(#<FUNCTION TEST NIL (DECLARE (SYSTEM::IN-DEFUN TEST)) (BLOCK TEST "f")>)
[3]> (funcall (car a))
"f"
[4]> (defun test () "g")
TEST
[5]> (funcall (car a))
"f"
[6]> 

elisp:

;; This is *scratch* buffer.

(defun test () "fff") => test

(setq a (list #'test)) => (test)

(funcall (car a)) =>  "fff"

(defun test () "ggg") => test

(funcall (car a)) => "ggg"
2 个赞

用[定义]这个词不太合适, 应该是依据函数的定义生成的函数对象 (function object)

2 个赞

Emacs 里面 lambda 是个宏,展开来是 #'(lambda ...),后者就是对 funtion 这个 special form 的调用。你说的没错。

对于 CL 来说,你说错了:不是列表,是闭包。CL 为了提高效率是尽量避免在 run time 用列表这种原始而低效的数据结构的。闭包在 CL 里面是一种特殊的数据结构,是不能被当作列表操作。

但是你这句话对于动态作用域的 Lisp,比如 NewLisp,没有开 lexical 绑定的 Emacs Lisp 是正确的。在这些解释性语言中,lambda 本质的确就是列表。下面这段 Emacs Lisp 代码可以证明。

(setq foo (lambda () 1))

foo ;; => (lambda nil 1)

(car foo) ;; => lambda

甚至对于开了 lexical binding 的 Emacs Lisp,你说 lambda 本质是列表也是基本正确的:

(setq lexical-binding t)

(lambda () 1) ;; => (closure (t) nil 1)

(car (lambda nil 1)) ;; => closure

;; 最有趣的要来了

(fset 'bar '(clozure (t) nil 1))

(bar) ;; => 1

;; 还不够刺激?

(funcall '(closure (t) nil 1)) ;; => 1

((closure (t) (x) x) 2) ;; => 2

从这里我们看到了 Common Lisp 和 Emacs Lisp 一个重要的区别就是,很多在 Common Lisp 中打印成不可读入的数据结构,在 Emacs Lisp 中是用可重新读入的方法表示的。

见我翻译的:

所以我加了引号。

2 个赞

另外,就算是对于 Emacs Lisp,你只考虑了在未编译的前提下的情况,字节码编译了以后就是完全不同的一回事。

(setq foo (byte-compile (lambda nil "foo")))
;; => #[nil "\300\207" ["foo"] 1 "foo"]
(funcall foo)
;; => "foo"
(car foo) ;; => Err
;; 做个对比
(listp foo) ;; => nil
(listp (lambda nil "foo")) ;; => t

lambda 作为函数编译以后很明显就不是列表了。

所以直接修改 lambda 在 Emacs Lisp 里面是黑科技,随便用不得。具体一些细节因为实在是太多,我不在这里赘述了。

2 个赞

这点我赞同, 也许如果想搞清楚这一块, 看源代码是最好的方式, 用黑箱技术猜测它的内部运作可能就不太现实了.

闭包这个东西,以前就是无法理解是什么东西, 后来研究 lisp 的求值规则和自由变量相关的知识,才理解了 这到底是个什么玩意.

单靠闭包的定义来理解闭包,那简直是天方夜谭.

对对,自由变量 + dynamic scope vs lexical scope,我当时正好接连看了SICP开头的dynamic scope vs lexical scope,dynamic scope可以setq改变函数行为,和王垠的《lisp已死,lisp万岁》(没链接,他的博客404了)里面说lisp最初是dynamic scope,后来有了更好的lexical scope,才理解了 这到底是个什么玩意。

后来又仿佛听说lexical scope实现起来就是每个scope生成一个closure,把这个scope的bindings全部放里面(看上面帖子对【clisp的function object、lexical binding的elisp的closure object、elisp byte compile后的byte code】的解析,这个说法应该没错),有忍不住要长叹一声“哦~~~”的感觉 就像零距离见过飞碟飞了之后又登上去摸了下仪表板


是是是!心疼那些整天被面试官问“什么是闭包”的javascript程序员(包括以前的我)


这个emacs-document能搞个rss啥的不?

用 GitHub 的 RSS 吧。


还好你们不用研究 Continuation。不然更要完。

另外 Emacs Lisp 的闭包不能在 interactive 模式测试,需要写成文件以后才能生效。

我他妈的也被这样坑过, 我感觉lisp里面的许多概念都是这么来的: 一群了解 lisp 核心的大牛 从 lisp 的基本规则 推倒出一个有意思的东西, 大家都在研究这个东西, 那就给它起一个简单的 名字吧, 然后一个计算机术语就出现了, 他们之做了一个简单的命名.就好比他们见过外星人,写了 一个什么是外星人.

但我们学习这个东西的时候, 往往是从定义开始的, 难度就好比你没见过外星人, 单凭外星人的描述 来理解什么是外星人一样. 极度的坑爹

另外一个例子就是: 什么是 symbol , 我一开始的时候被这个问题困惑了很久, 根本不理解那些 文献或者教材里面的定义到底在说啥, 等我稍微了解了一下 lisp 的内部运行机制, 我发现这玩意 其实是一个非常简单的东西, 但让我说 symbol 是什么, 我只能说 符号就是符号,用就行了…

A symbol is an object with a simple string representation that (by default) is guaranteed to be interned; i.e., any two symbols that are written the same are the same object in memory (reference equality).

1 个赞

我估计初学者是最恨这种定义了, 单纯的描述, 就好比将斧头定义为一个木头把上面有一个铁块的物体…

这个嘛,需要稍微了解一下 C 和计算机原理,知道各种数据类型在内存中是如何表示的,指针是个啥玩意,大概就懂了。虽然 Lisp 是高度抽象的语言,但实现还是受到计算机结构限制的。

一开始学习的话,就只能用行为来推导了。

Symbol和Pointer是困扰我很多年的东西。现在稍微对Pointer有些认识了,但是Symbol还是停留在“能用,但是不知道是啥”的阶段

推荐读 《Low Level Programing》

这条不对吧。

1 个赞

嗯,我对 Lisp 的 variable catch 理解出了偏差。用 setq 设的变量不會被捕獲。

“变量捕捉“ 这个术语有时候很有误导性

(let ((x 1))
  (lambda () x))

以前我一直认为 “变量捕捉” 的意思是, 第一个 x 把第二个 x 给捉住了的意思。 后来看文献才发现, 似乎不是这么理解的, “变量捕捉“ 里面的变量是指 “自由变量“, 它是指第二个x, 第二个x被第一个x构建的笼子给限制住了,失去了原来自由性,所以就叫 “变量捕捉”。。。。

所以,我的结论是: 变量捕捉和变量赋值,其实是类似的, 唯一的不同可能就是: 变量赋值是直接赋值,而变量捕捉,是一种环境间接赋值法, 总归来说都是赋值操作。

进一步脑补: 既然变量赋值有直接赋值和间接赋值两种方式, 那创建变量是不是也应该有两种方式呢? :grimacing: