确实 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 个赞
LdBeth
2018 年1 月 13 日 06:14
15
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 中是用可重新读入的方法表示的。
见我翻译的:
tumashu:
用[定义]这个词不太合适
所以我加了引号。
2 个赞
LdBeth
2018 年1 月 13 日 06:41
16
另外,就算是对于 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 个赞
这点我赞同, 也许如果想搞清楚这一块, 看源代码是最好的方式, 用黑箱技术猜测它的内部运作可能就不太现实了.
LdBeth:
对于 CL 来说,你说错了:不是列表,是闭包。
闭包这个东西,以前就是无法理解是什么东西, 后来研究 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】的解析,这个说法应该没错),有忍不住要长叹一声“哦~~~”的感觉 就像零距离见过飞碟飞了之后又登上去摸了下仪表板
tumashu:
单靠闭包的定义来理解闭包,那简直是天方夜谭.
是是是!心疼那些整天被面试官问“什么是闭包”的javascript程序员(包括以前的我)
这个emacs-document能搞个rss啥的不?
LdBeth
2018 年1 月 14 日 03:50
20
用 GitHub 的 RSS 吧。
还好你们不用研究 Continuation。不然更要完。
另外 Emacs Lisp 的闭包不能在 interactive 模式测试,需要写成文件以后才能生效。
我他妈的也被这样坑过, 我感觉lisp里面的许多概念都是这么来的: 一群了解 lisp 核心的大牛
从 lisp 的基本规则 推倒出一个有意思的东西, 大家都在研究这个东西, 那就给它起一个简单的
名字吧, 然后一个计算机术语就出现了, 他们之做了一个简单的命名.就好比他们见过外星人,写了
一个什么是外星人.
但我们学习这个东西的时候, 往往是从定义开始的, 难度就好比你没见过外星人, 单凭外星人的描述
来理解什么是外星人一样. 极度的坑爹
另外一个例子就是: 什么是 symbol , 我一开始的时候被这个问题困惑了很久, 根本不理解那些
文献或者教材里面的定义到底在说啥, 等我稍微了解了一下 lisp 的内部运行机制, 我发现这玩意
其实是一个非常简单的东西, 但让我说 symbol 是什么, 我只能说 符号就是符号,用就行了…
LdBeth
2018 年1 月 14 日 04:29
22
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 个赞
我估计初学者是最恨这种定义了, 单纯的描述, 就好比将斧头定义为一个木头把上面有一个铁块的物体…
LdBeth
2018 年1 月 14 日 04:36
24
这个嘛,需要稍微了解一下 C 和计算机原理,知道各种数据类型在内存中是如何表示的,指针是个啥玩意,大概就懂了。虽然 Lisp 是高度抽象的语言,但实现还是受到计算机结构限制的。
一开始学习的话,就只能用行为来推导了。
Symbol和Pointer是困扰我很多年的东西。现在稍微对Pointer有些认识了,但是Symbol还是停留在“能用,但是不知道是啥”的阶段
LdBeth
2018 年1 月 14 日 05:52
26
推荐读 《Low Level Programing》
LdBeth
2018 年1 月 14 日 11:58
28
嗯,我对 Lisp 的 variable catch 理解出了偏差。用 setq
设的变量不會被捕獲。
“变量捕捉“ 这个术语有时候很有误导性
(let ((x 1))
(lambda () x))
以前我一直认为 “变量捕捉” 的意思是, 第一个 x 把第二个 x 给捉住了的意思。
后来看文献才发现, 似乎不是这么理解的, “变量捕捉“ 里面的变量是指 “自由变量“, 它是指第二个x, 第二个x被第一个x构建的笼子给限制住了,失去了原来自由性,所以就叫 “变量捕捉”。。。。
所以,我的结论是: 变量捕捉和变量赋值,其实是类似的, 唯一的不同可能就是: 变量赋值是直接赋值,而变量捕捉,是一种环境间接赋值法, 总归来说都是赋值操作。
进一步脑补: 既然变量赋值有直接赋值和间接赋值两种方式, 那创建变量是不是也应该有两种方式呢?
LdBeth:
用 setq 设的变量不會被捕獲。
以前在这一块我也混乱过, 在 elisp 中, 存在两种独立的操作:
创建变量操作
变量赋值操作
创建变量操作最常见的有两种: 全局的变量创建用 defvar, 局部的变量创建用let,
而 setq 是变量赋值操作,
可是大家习惯直接用 setq 创建并赋值变量,虽然省事,但严格来讲,是不太合理的。
所以这句话这样说可能更好: 已经赋值的变量没有自由性,所以不会被环境捕获,只有自由变量才存在变量捕捉的问题。
另外一个问题是: 自由变量为什么一定要被捕捉? 其实很简单,因为 lisp 语法
规定了, 自由变量必须被求值,不被赋值,怎么被求值呢?
LdBeth:
《Low Level Programing》
《Low Level Programing》 这本书作者是?或是有链接吗?我搜了下感觉没找到对应的书