复习 Lisp: Syntax and Semantics

确实 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:

以前在这一块我也混乱过, 在 elisp 中, 存在两种独立的操作:

  1. 创建变量操作
  2. 变量赋值操作

创建变量操作最常见的有两种: 全局的变量创建用 defvar, 局部的变量创建用let,

而 setq 是变量赋值操作,

可是大家习惯直接用 setq 创建并赋值变量,虽然省事,但严格来讲,是不太合理的。

所以这句话这样说可能更好: 已经赋值的变量没有自由性,所以不会被环境捕获,只有自由变量才存在变量捕捉的问题。

另外一个问题是: 自由变量为什么一定要被捕捉? 其实很简单,因为 lisp 语法 规定了, 自由变量必须被求值,不被赋值,怎么被求值呢?

《Low Level Programing》 这本书作者是?或是有链接吗?我搜了下感觉没找到对应的书