openlang

Try a dynamic let before advice-add

Advice is the nicest thing about extending a running editor and also the thing I trust least in other people's init files. (Mine included.)

advice-add with :around wraps a function without touching its definition, and advice-remove takes the wrapper off again. You can do all of it from *scratch* while the editor keeps running, which is the whole charm. The catch is that advice applies to every caller, including packages that never asked for it. describe-function does list active advice in the help buffer, so C-h f on the misbehaving function is the first thing to try when something acts strange.

The other tool people forget is dynamic scope. A variable defined with defvar is special, so a let around a call rebinds it for everything that call reaches, all the way down, and the old value comes back when the let exits. With lexical-binding on, ordinary locals are lexical but those special variables stay dynamic. Binding case-fold-search around the one call you care about is often cleaner than advising anything.

My rough order. A hook if the package offers one, a dynamic let if a variable controls the behavior, advice last.

后续讨论 3

顺着彼此的依据读下去
  1. 固定链接

    The first thing I ask is which threads see the change, because that's what separates your let from advice. In Clojure, binding on a ^:dynamic var is per thread and undone when the form exits. alter-var-root changes the root for every thread, which is what advice amounts to. with-redefs looks scoped, but it swaps the root temporarily, so other threads see the swap while it lasts.

    Run two tests that use with-redefs on the same function in parallel. Which definition does each one get? Your let around case-fold-search is the binding case. Advice is the root case, and it has to be reasoned about as a global change.

  2. 固定链接

    lichen's question sent me to the Threads chapter, and Emacs answers it the way Clojure does. The manual says a dynamic let binding is local to its thread while global variables are shared, and a thread made with make-thread starts with no local bindings in effect.

    Advice sits on the function itself, so every thread gets it. Your mapping holds, let is binding and advice is the root, so advice stays at the bottom of my list.

  3. 固定链接

    Here's one to guess before running it. my-level is special, the let rebinds it, and bump assigns to it. What do the two messages print?

    (defvar my-level 1)
    (defun bump () (setq my-level (1+ my-level)))
    (let ((my-level 10))
      (bump)
      (message "%s" my-level))
    (message "%s" my-level)

    11, then 1. The setq inside bump lands on the innermost binding, which belongs to the let, so the change goes away when the let exits. Anyone expecting a global 2 afterwards is reading setq as "set the global", and for a special variable it means "set whichever binding is current". That's also why a dynamic binding is safe to wrap around code that assigns to the variable.

同一个问题的其他角度

  1. My function returns an array when all I asked for was a path

    Writing a PowerShell function from scratch for once (rather than squinting at someone else's), and it misbehaves in a way I c…

    回复 4最近活动 powershell
  2. My init set fill-column for exactly one buffer

    Had (setq fill-column 72) in my init for ages and only just noticed that M-q in a fresh buffer still wraps at 70. (In *scratc…

    回复 8最近活动 emacs-lisp
  3. Upgradeable means the storage layout is forever

    People hear proxy pattern and think the immutability problem went away. It moved. The proxy holds the state and forwards call…

    回复 3最近活动 solidity