求助:宏展开的问题?

折腾了好久,实在没办法了。 需要修改一下下面这个函数的定义,修改的地方就是在unless那行加了个or。


(defun persp-add-buffer (buffer)
  "Associate BUFFER with the current perspective.
See also `persp-switch' and `persp-remove-buffer'."
  (interactive
   (list
    (let ((read-buffer-function nil))
      (read-buffer "Add buffer to perspective: "))))
  (let ((buffer (get-buffer buffer)))
    (unless (or (not (persp-curr)) (memq buffer (persp-buffers (persp-curr))))
      (push buffer (persp-buffers (persp-curr))))))

但是无论是def还是加advice,都报错,symbol’s function definition is void: \(setf\ persp-buffers\),感觉像是宏展开的错误。还有这里面的persp-buffers不是一个函数,而是由setf定义的一个位置,不是很懂。

源文件在这里 perspective-el/perspective.el at master · nex3/perspective-el · GitHub

把配置编译了之后就没问题了,去除编译文件运行还是有问题。

perspective这个包总是遇到莫名其妙的问题,还是自己对elisp了解不够。

因为你把这个放在 perspective 加载之前了。

我放在use-package的:config里了,这样子不行吗?

不行,因为 setf 的宏展开在 use-package 的时候就调用了

我就感觉跟这个setf有关,感谢!

吃完午饭 略饱 然后温饱思淫欲 想睡觉, 幸好随手点开emacs-china看看想学习的东西,碰巧楼主问题置顶,然后…

我看到了这个问题后习惯性的去看源码学习,然后看到 cl-defstruct, 看不懂,于是看文档,惊喜来了:

感谢楼主大佬给了我学到一个elisp面向对象的编程方法机会!

虽然很多lisp概念还是无法深识,但是我目前想到了一个笨办法,鉴于要使用use-package管理perspective,得出我的原始方法是:

(use-package perspective
  :config
  
  (cl-defstruct (perspective
                 (:conc-name persp-)
                 (:constructor make-persp-internal))
    name buffers killed local-variables
    (buffer-history buffer-name-history)
    (window-configuration (current-window-configuration))
    (point-marker (point-marker)))

  ;; redefine persp-add-buffer
  (defun persp-add-buffer (buffer)
    "Associate BUFFER with the current perspective.
See also `persp-switch' and `persp-remove-buffer'."
    (interactive
     (list
      (let ((read-buffer-function nil))
        (read-buffer "Add buffer to perspective: "))))
    (let ((buffer (get-buffer buffer)))
      (unless (or (not (persp-curr)) (memq buffer (persp-buffers (persp-curr))))
        (push buffer (persp-buffers (persp-curr)))))))

就如 @LdBeth 大佬所教的, 我想是因为自定义数据类型(或者叫‘’类‘’,我不是很清楚)的缺失,导致成员方法的无法引用,也就产生了找不到定义的问题,因此我把结构体随重定义一起塞进use-package:config中了。

虽然我不用perspective, 但是可能有效,如果有效也不知道这个方法是不是规范。

学到了,才知道这东西后端是怎么做的。

不需要这么麻煩(?),而且虽然为了方便调试 elisp 允許这么做,重定义 struct 的確是不规范的。

byte compile 是最理想解。但如果完全不想 byte compile 配置,可以:

...
:config
(eval '(byte-compile
        (defun persp-add-buffer (buffer)
          "Associate BUFFER with the current perspective.
See also `persp-switch' and `persp-remove-buffer'."
          (interactive
           (list
            (let ((read-buffer-function nil))
              (read-buffer "Add buffer to perspective: "))))
          (let ((buffer (get-buffer buffer)))
            (unless (or (not (persp-curr)) (memq buffer (persp-buffers (persp-curr))))
              (push buffer (persp-buffers (persp-curr))))))))

没看明白,跟宏有什么关系。

像下边这样使用 advice 会有问题?

(defun my-persp-add-buffer (buffer)
  ;; ...
  )

(with-eval-after-load 'perspective
  (advice-add 'persp-add-buffer :override 'my-persp-add-buffer))

;; or

(defadvice persp-add-buffer (around my-persp-add-buffer (buffer) activate)
  "Associate BUFFER with the current perspective.
See also `persp-switch' and `persp-remove-buffer'."
  (interactive
   (list
    (let ((read-buffer-function nil))
      (read-buffer "Add buffer to perspective: "))))
  (let ((buffer (get-buffer buffer)))
    (unless (or (not (persp-curr)) (memq buffer (persp-buffers (persp-curr))))
      (push buffer (persp-buffers (persp-curr))))))

会。问題不在于 advice,在于 push

(push buffer (persp-buffers (persp-curr)))

单从行为上可以理解为 push 调用了 setf 的內部宏展開,即

(defmacro push (new place)
  `(setf ,place ,new))

而在用 setf 修改一个 struct 时,宏展开要用到对应的 self expander,以上面的 persp-buffers 为例,会调用一名为 \(setf\ persp-buffers\) 的 local function。 而 \(setf\ persp-buffers\)cl-defstruct 定义,在 perspective 未加載,以及配置未 byte compile 以提前宏展开时,就会出現如題所示的

Lisp error: (void-function \(setf\ persp-buffers\))

所以这里跟宏和 advice 都没有关系,是楼主的加载流程出了问题。

persp-add-buffer 不应该在 perspective 加载之前调用,基于错误的加载流程提出的解决方案也是不可取。

大佬 感谢你的纠正,但是 我还是不明白为什么 byte-compile 后就没问题。

重点是!!!!!! 我怀疑楼主的问题根本不会出现,因为我想复现楼主的问题,但是复现不出来?

很抱歉我在没有试验问题的可复现性的时候就草率的回复楼主(那时第一次看到 cl-defstruct 高兴坏了 :smile: ),但是确实这对于我以下的复现实验结果是相悖的,又由于我稍微看了一下use-packge源码,因为发现use-package本身是宏,因此猜测它只是生成配置的form-list,然后按条件把list中的元素分配给其他函。再来(defun ....) 在我的实验下,这个表达式的执行时不会进行定义检查的,我觉得可能只是建立一个symbol的函数域中数据结构罢了,好了前面说的是猜测,我摆上我的测试代码(:wink: 偷了 @twlz0ne 大佬的一部分, 懒的手写了 :

(use-package perspective
  :init (persp-mode)
  :config
;;   (defadvice persp-add-buffer (around my-persp-add-buffer (buffer) activate)
;;     "Associate BUFFER with the current perspective.
;; See also `persp-switch' and `persp-remove-buffer'."
;;     (interactive
;;      (list
;;       (let ((read-buffer-function nil))
;;         (read-buffer "Add buffer to perspective: "))))
;;     (let ((buffer (get-buffer buffer)))
;;       (unless (or (not (persp-curr)) (memq buffer (persp-buffers (persp-curr))))
;;         (push buffer (persp-buffers (persp-curr))))))

  (advice-add 'persp-add-buffer :override #'my/persp-add-buffer)

  
  (defun my/persp-add-buffer (buffer)
    "Associate BUFFER with the current perspective.
See also `persp-switch' and `persp-remove-buffer'."
    (interactive
     (list
      (let ((read-buffer-function nil))
        (read-buffer "Add buffer to perspective: "))))
    (let ((buffer (get-buffer buffer)))
      (unless (or (not (persp-curr)) (memq buffer (persp-buffers (persp-curr))))
        (push buffer (persp-buffers (persp-curr)))))))

重新启动emacs结果一切正常。。。。

你没有用 autoload,或者说用了也没延迟加载,因为 :init 里面直接把这个包加载了。

:init 那行去了再试试。

yes 是的 我考虑到启动就要加载perspective就这么配置了, :rofl:

但是我不明白如果不启动 (persp-mode) 当然就没法使用这个包的内部函数啊,也就是说大佬教我怎么用autoload 换init::smile:

使用 (add-hook 'after-init-hook #'persp-mode) 我再试一下。

反正题主没一开始就启动。你要复现问题研究一下就都去了,要是只是确认自己配置有没有文件结论已经很明显了,没问题。

用了这个和在 :init 开没区别。

使用 (add-hook 'after-init-hook #'persp-mode) 我再试一下。

let*: Symbol’s function definition is void: (setf\ persp-buffers) [2 times]

出错了

这不就复现问题了嘛。

不用猜,直接用 macroexpand-1 看。

大佬谢啦 macroexpand-1