不可能就地(In-place)地删除列表的第一个元素

但其它位置的元素都是可以的。

(defun delete-nth (n l)
  (when (> n 0)
    (let ((x (nthcdr (1- n) l)))
      (setcdr x (cddr x)))))

可以删除第 2 、3、4 等等个元素:

;; 删除第 2 个元素
(let ((l '(a b c d)))
  (delete-nth 1 l)
  l)
;; => (a c d)

;; 删除第 3 个元素
(let ((l '(a b c d)))
  (delete-nth 2 l)
  l)
;; => (a b d)

但就是不能删除第 1 个元素,换言之,无论 delete-nth 怎么写都通不过下面的测试。

(let ((l '(a b c d)))
  (cl-assert (functionp 'delete-nth))
  (delete-nth 0 l)
  (cl-assert (equal l '(b c d))))
1 个赞

因为emacs的list是基于cons cell实现的吧

1 个赞

我都是写一个自制函数,序号是0的话就用setcar。但是说实话不如写一个non-destructive 的 macro(类似push)

怪不得我有印象讨论过,我有预感得清楚 Emacs 的 Lisp Machine 的具体实现才能真正理解怎么回事,而且也不是不可能,或许只是 Emacs 没提供相应的函数,比如 Emacs 或许也可以不提供 setcdr

随手敲了一个

(defun delete-head (list)
         (prog1
             (setcar list (cdr list))
           (setcdr list nil)))
(let ((l '(a b c d)))
  (delete-head l)
  l) ;; => ((b c d))
(defun delete-head (list)
  (setcar list (cadr list))
  (setcdr list (cddr list))
  list)

放心好了,不用挣扎了,因为在 lisp 里

(defun i-want-a-procedure (x) (setq x 3))

是不会起作用的,函数内会 shadow 上一层环境。要直接操作环境只能通过 symbol-value 来。

1 个赞

那改成macro吧


ELISP> lexical-binding
t
ELISP> (let ((ok '(3 4 5)))
         (funcall (lambda (ok)
                    (nreverse ok)) ok)
         ok)
(3)

ELISP> (let ((ok (list 3 4 5)))
         (funcall (lambda (ok)
                    (nreverse ok)) ok)
         ok)
(3)

@LdBeth ??

Elisp应该是pass by ref的吧


试了下,SBCL里这么写也会吧原列表爆破掉

For nreverse, sequence might be destroyed and re-used to produce the result. The result might or might not be identical to sequence .

(nreverse l) =>  (3 2 1)
 l =>  implementation-dependent

for example, CLISP

[3]> (let ((ok '(3 4 5)))
         (funcall (lambda (ok)
                    (nreverse ok)) ok)
         ok)
(5 4 3)

Lisp 都是 pass by reference,但是沒有 Pascal/Ada/C 的那种 out parameter。虽然現在都不鼓励用 out parameter 了。

Procedure WriteToB(Out B : Integer);  
begin  
  B:=2;  
end;  

Call by value/call by reference/call by sharing/pass by reference 混在一起傻傻分不清。

The description “call by value where the value is a reference” is common (but should not be understood as being call by reference); another term is call by sharing. Thus the behaviour of call by value Java or Visual Basic and call by value C or Pascal are significantly different: in C or Pascal, calling a function with a large structure as an argument will cause the entire structure to be copied (except if it’s actually a reference to a structure), potentially causing serious performance degradation, and mutations to the structure are invisible to the caller. However, in Java or Visual Basic only the reference to the structure is copied, which is fast, and mutations to the structure are visible to the caller.

反正 Lisp 是和 Java 一樣的,不支持 out parameter,但物件会共享状态。

1 个赞

很多這類(modified by side effects)函數都是要立即應用setq吧,像(setq a (sort a #'<))。結合setcar可以in-place刪除元素多餘1個的列表,但對於singleton也許不能in-place改成nil?

你这种带ELISP>提示符,是装的什么插件?

M-x ielm


不是 helm,打的 ielm 被垃圾自动拼写纠正给改了。Emacs 自帯就有。

好的,多谢!

有些测试用代码,我是直接写在一个test.el之类的文件里,留以后查看。不知大家是平常怎么弄的?