Doom Emacs 函数变量后面的感叹号怎么回事?

Doom Emacs 函数变量后面的感叹号怎么回事?

use-package! load!

是什么elisp最佳实践还是什么

就是一些自定义的宏

这样听起来很劲爆! 其实就是魔改了之后不知道取啥名

2赞

这似乎是lisp变量命名的一个传统。其实我倒是想了解一些这方面的约定,比如说类似elisp里面函数名中间有两条横线的一般是指内部函数

@cireu 有讨论 https://cireu.github.io/2019/10/18/doom-emacs-issue/#被滥用的宏

肯定不是。

Racket 里 ! 一般表示这个函数有副作用。

也是一直觉得就是一些命名习惯

今天看了一些 Typescript react 代码,看context! 就很纳闷 然后意识到 JS 变量/函数名 后面不能有 !

  static contextType = ThemeContext;
  context!: React.ContextType<typeof ThemeContext>;

搜了一番后 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator

看到‘宏的 “重载”’这段:

add-hook! 宏上支持各种各样的 “重载”[3], 对比两段等价代码的不同写法

(add-hook! 'emacs-lisp-mode-hook 'ignore)
(add-hook! emacs-lisp-mode 'ignore)

这里头的奥秘在于 emacs-lisp-mode 没有 quote 的时候 add-hook! 宏会自动给你加上一 个 -hook 的后缀。看起来是可以节省打字功夫的精巧设计,然而可能作者自己也记不住这 些规则,doom-emacs 里有时用起 'xxx-hook , 有时直接用 xxx . 不如直接统一用 'xxx-hook , 和 Emacs 的 add-hook 保持一致.

我想到的是我经常搞不清楚 Emacs 一些内置方法的使用,例如 (append &rest SEQUENCES),参数到底是什么形式的,目标在前还是在后,是否是 list,需不需要 quote …,所以我都用 snippet 来辅助输入:

(append ${1:to-list} ${2:from-list})

还有 add-to-list

(add-to-list '${1:list-var} ${2:element})

Doom 的作者是不是也经常为类似的情况犯愁,所以干脆就把差别模糊处理掉了,用的时候想咋写就咋写。

! 感叹号,如其本意,警示引起注意的作用。

比如 set! setcar! 就是警示用户, 此处要变轨道了, 不再应用 fp 思维,切换到 procedurual 思维了, 比如 赋值操作。

1赞

可以用seq.el

(seq-concatenate 'list seq1 seq2 seq3 seq4)

因为这样听起来很劲爆吧,比较符合doom这个名字。在Doom这(几)部游戏里,主角就是做什么动作力气都很大、很干净利落,能一拳锤爆的恶魔绝不用两拳。doom emacs有个discord频道,据说有问题作者都会秒回,可以去那里问。

谢谢提醒,去doom repo 看了下。发现有discord 里面贴了 Contributing (WIP): https://github.com/hlissner/doom-emacs/blob/develop/docs/contributing.org

里面有 Doom Naming Conventions

然后有解释到 abc!

和typescript 的 Non-null assertion operator 有类似的概念 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator

我现在有点能理解了

abc!

A public Doom “autodef” function or macro. An autodef should always be defined, even if its containing module is disabled (i.e. they will not throw a void-function error). The purpose of this is to avoid peppering module configs with conditionals or after! blocks before using their APIs. They should noop if their module is disabled, and should be zero-cost in the case their module is disabled.

Autodefs usually serve to configure Doom or a module. e.g. after! , set-company-backends! , set-evil-initial-state!

我常常搞不清 nth aref elt 的参数顺序,还好 eldoc 帮了很大忙

函数和宏分开考虑,函数的话,需要变量、函数和符号用 quote (函数应该用 function quote,比如上面的 add-hook 的 ignore 用 #'ignore);宏的话一般不用 quote。除了文档,看 demo 能很快搞清楚。这几个我有时会搞不清

define-key add-hook advice-add add-to-list push

to-list 和 from-list 是什么意思?应该是第一个 list 和 第二个 list 吧,把它们拼接起来。

以前经常搞不清楚目标参数,把大列表(通常是目标,故命名为 to-list)追加到小列表,导致性能损耗。

不过我刚才又验证了一遍,发现我竟然搞反了😅

(defvar l100 (number-sequence 1 100))
(defvar l1   '(1))

(dolist (i (number-sequence 1 10))
  (message "benchmark[%2s] (append l1 l100) => %s"
           i (benchmark-run 10000 (append l1 l100))))

(dolist (i (number-sequence 1 10))
  (message "benchmark[%2s] (append l100 l1) => %s"
           i (benchmark-run 10000 (append l100 l1))))

;; =>
;; benchmark[ 1] (append l1 l100) => (0.055618 1 0.026161)
;; benchmark[ 2] (append l1 l100) => (0.003313 0 0.0)
;; benchmark[ 3] (append l1 l100) => (0.0013179999999999997 0 0.0)
;; benchmark[ 4] (append l1 l100) => (0.0024860000000000004 0 0.0)
;; benchmark[ 5] (append l1 l100) => (0.021117 1 0.019425)
;; benchmark[ 6] (append l1 l100) => (0.0017670000000000001 0 0.0)
;; benchmark[ 7] (append l1 l100) => (0.0014750000000000002 0 0.0)
;; benchmark[ 8] (append l1 l100) => (0.001558 0 0.0)
;; benchmark[ 9] (append l1 l100) => (0.0030680000000000004 0 0.0)
;; benchmark[10] (append l1 l100) => (0.020804 1 0.019367999999999996)
;; benchmark[ 1] (append l100 l1) => (0.37316499999999997 20 0.354604)
;; benchmark[ 2] (append l100 l1) => (0.365996 20 0.34868200000000005)
;; benchmark[ 3] (append l100 l1) => (0.869162 20 0.839315)
;; benchmark[ 4] (append l100 l1) => (0.569689 20 0.5476769999999997)
;; benchmark[ 5] (append l100 l1) => (0.38914699999999997 20 0.37303200000000025)
;; benchmark[ 6] (append l100 l1) => (0.381527 20 0.3640970000000001)
;; benchmark[ 7] (append l100 l1) => (0.366391 20 0.3495689999999998)
;; benchmark[ 8] (append l100 l1) => (0.36312099999999997 20 0.3460610000000002)
;; benchmark[ 9] (append l100 l1) => (0.37635599999999997 20 0.3593139999999999)
;; benchmark[10] (append l100 l1) => (0.35456299999999996 20 0.3406659999999997)

append是找链表尾巴然后接上去,找链表尾巴的时间复杂度是O(n),当然找小链表的尾巴比较快

就是搞不清楚哪一边是头,以前意识到了问题,所以特意标注起来,没想到标错了。

所以,不应该区分目标/来源,这样容易把自己带沟里,而是要注意大/小。

内部的定长数据结构我都用vector……只有对用户接口才用list

性能差距不大吧,写起来还麻烦

不对吧? (append SEQ1 SEQ2 SEQ3 ... SEQN) 依次拷贝前 N - 1 个 SEQ 然后拼接起来

The last argument is not copied, just used as the tail of the new list.

拷贝才是主要的耗时操作?


考虑性能的话,使用 nconc,放弃维护原值。当需要维护原值时,用 copy-sequence 保留一份。当确认没有必要 append 时,也可以优先用 append,当然用户要清楚两者区别,否则用 append 不容易出错。