请教一个(并不)低级的plist问题

这个实验我也做过。这样就可以,那为什么我那个不是呢?

一样是 '(key1 value1 key2 value2) 的结构啊!

这里参数顺序写反了。我也常犯这样的错误。考虑到顺序之不一致,不会记错才怪:

(plist-get plist 'key)
(alist-get 'key alist)
(gethash 'key hash-table)

(nth n list)
(elt seq n)
(aref array idx)

抱歉,整理 片段代码的时候写错了。

(plist-get a-plist 'NAME) ; --> 也是nil

一定是你的 a-plist 有问题,把实际的 a-plist 打印出来,实验结果不等于实际情况。

好思路,我把work的plist打印出来比较一翻。

其实我想看看plist-get这个宏的,但是发现它竟然是c代码实现。

你没找到问题是什么,就先假定了跟 plist-get 有什么关系。

collect (intern (plist-get child-val :key))

2 个赞

解釋一下:

make-symbolintern 的生成的 symbol 对於 plist-get 有区別的原因在于,plist-get (可以理解为)是用 eq 比較 symbol 的,而 make-symbol 生成的 symbol 是不能用 eq 比較的。

(eq 'ADS (make-symbol "ADS"))
nil

(eq (make-symbol "ADS") (make-symbol "ADS"))
nil

(eq 'ADS (intern "ADS"))
t

具體原理涉及 call-by-reference,即指针之类的。简言之 eq 就是比較两個指针。intern 生成的 symbol 是返回指针的,且对于同一個 symbol 返回的是同一個指针,而 make-symbol 生成的 symbol 不返回指针,故而不能用 eq 比較。

参考 Emacs Lisp 的 symbol 实現:Creating Symbols (GNU Emacs Lisp Reference Manual)

3 个赞

补充一句, lisp 添加这种特性,一个用途(也可能是主要的)是为了防止编写宏的时候, 出现变量冲突。。。。。

还有一種说法是以前內存很宝貴,用 uninterned symbol 可以节约內存。:joy:

:joy::joy::joy::joy::joy::joy: 其实有时候研究 lisp 历史很有意思, 你会发现现在难以理解的特性的历史背景

之前都没注意到有 make-symbol 这个函数。看了一下它的定义,跟 intern 确实不太一样:

  • intern 首先会根据参数 STRING 从表中查找现存的符号,如果不存在则创建。所以两次 (intern "foo") 返回的是同一个 symbol;而两次 (make-symbol "foo") 返回的是不同的 symbol,所以 eq 比较结果为 nil。
  • intern 返回的 symbol,有对应的 value;而 make-symbol 返回的 symbol,其对应的 value 是 void

源代码可以看出两个函数到底干了啥:

 Elisp           | C
-----------------|----------------------------------------
 (intern STRING) | ...
                 | tem = oblookup(obarray, ...STRING, ...);
                 | ...
                 | return tem;
 Elisp              | C
--------------------|----------------------------------------
 (make-symbol NAME) | ...
                    | init_symbol (val, name); 
                    |    .--^-------------------------------------.
                    |    | struct Lisp_Symbol *p = XSYMBOL (val); |
                    |    | ...                                    |
                    |    | set_symbol_name (val, name);           |
                    |    | ...                                    |
                    |    | SET_SYMBOL_VAL (p, Qunbound);          |
                    |    '----------------------------------------'
                    | ... 
                    | return val;
2 个赞

@tumashu @LdBeth @twlz0ne 非常感谢各位的详细解释~ 被坑得深刻。话说,我貌似曾经在哪里看过这两个区别(一篇写Symbol的文章),但是那时候对lisp只是走马观花。上次做东西,只想着 string -> symbol,所以从C-h f里搜索到的就是make-symbol :joy:

但是一直不work,我就改用hash绕过了,被坑了之后,现在几乎一直都在用hash。

顺便关联一下:

make-symbol跟 es6的Symbol一样 Symbol('hehe') !== Symbol('hehe') --> true

答案就按顺序取第一位回答到点子上的 @tumashu 吧。

感觉该去弄一份emacs的c源码跟 emacs的doc关联起来,这样遇到c实现的函数,可以打开“轻查”一翻。

hash table 好啊,其实 plist 这种和列表相关不应该被鼓励当作数据结构使用的,效率比较低,然后有意外被副作用修改的风险。

直接 M-. 跳进去就能看doc和源码了,或者推荐 helpful 包

你再看看10楼和14楼呢,你这里的make-symbol改成intern才对。


这些你贴的文档里都没讲,按文档说的,这样解释就够了?:

intern去obarray找,找不到就创建,而make-symbol是独立于obarray创建的uninterned symbol

我讲的是概念,文档里的是具体实现,是相互补充的。是否返回指针(引用)的区别就是由是否从 obarray 返回值实现的,

大概是你讲的那部分没有提供相应资料所以让人有点将信将疑的感觉? :joy:

因为是结合 Common Lisp 的经验自己总结的……实际上没有太多 Emacs Lisp 专门的资料可以引用。只看 eq intern 的说明其实是看不出什么的。

https://whispering-beach-14031.herokuapp.com/eq

只说了测试两个物件是否是同一个物件。

eq 是比较指针的说法是这里来的

(Implementationally, x and y are usually eq if and only if they address the same identical memory location.) Implementation note: eq simply compares the two given pointers, so …

https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node74.html

其实用指针这个说法也只是为了理解方便,Lisp 和 Java 一样,严格来说都是没有指针的,只有引用,只不过引用可以通过指针实现,也可以像 Emacs Lisp 一样用 obarray 实现,只是要解释 obarray 是什么又要赘述一番,于是就留个链接让有兴趣“了解真相”的同学自己看原理了。

intern 的意义也是反向结合 Emacs Lisp 的实现以及 call by reference 的前提理解出来的。所以会说是参考了实现的文档。

1 个赞