diff --git a/lisp/demos.org b/lisp/demos.org index 3572ce417..dd10ae570 100644 --- a/lisp/demos.org +++ b/lisp/demos.org @@ -361,6 +361,71 @@ Or to create aliases for functions that behave differently: (:baz hello :boop nil) (:bar 42))) #+end_src +* letf! +:PROPERTIES: +:added: 3.0.0-pre +:END: +#+begin_src emacs-lisp :eval yes +(letf! (defun greet (name) + (message "Hello %s" name)) + (greet "Doom")) ; #=> Hello Doom +#+end_src + +#+RESULTS: +: Hello Doom + +Multiple definitions: +#+begin_src emacs-lisp :eval yes :results output +(letf! ((defun greet (name) + (princ (format "Hello %s" name)) + (terpri)) + (defun destroy (name) + (princ (format "Blood for the %s god!" name)) + (terpri))) + (greet "Doom") + (destroy "Doom")) +#+end_src + +#+RESULTS: +: Hello Doom +: Blood for the Doom god! + +If defining an already-existing function, the old definition will be bound to a +variable by the same name: +#+begin_src emacs-lisp :eval yes :results output +(letf! (defun princ (str) + (funcall princ (format "[overwritten] %s" str))) + (princ "Doom Emacs")) +#+end_src + +#+RESULTS: +: [overwritten] Doom Emacs + +Defining temporary advice: +#+begin_src emacs-lisp :eval yes +(letf! ((defun advised-message (fn message &rest args) + (apply fn (concat "[advised (1)] " message) args)) + (defadvice #'message :around #'advised-message) + (defadvice message (:around (fn message &rest args)) + (apply fn (concat "[advised (2)] " message) args))) + (message "Hello world")) +#+end_src + +#+RESULTS: +: [advised (1)] [advised (2)] Hello world + +Calling "lexical" functions recursively: +#+begin_src emacs-lisp :eval yes +(letf! (defun* triangle (number) + (cond ((<= number 0) 0) + ((= number 1) 1) + ((> number 1) + (+ number (triangle (1- number)))))) + (triangle 5)) +#+end_src + +#+RESULTS: +: 15 * load! :PROPERTIES: diff --git a/lisp/doom-lib.el b/lisp/doom-lib.el index b253bf6eb..31b7851e1 100644 --- a/lisp/doom-lib.el +++ b/lisp/doom-lib.el @@ -332,24 +332,26 @@ TRIGGER-HOOK is a list of quoted hooks and/or sharp-quoted functions." "Temporarily rebind function, macros, and advice in BODY. Intended as syntax sugar for `cl-letf', `cl-labels', `cl-macrolet', and -temporary advice. +temporary advice (`define-advice'). BINDINGS is either: - A list of, or a single, `defun', `defun*', `defmacro', or `defadvice' forms. A list of (PLACE VALUE) bindings as `cl-letf*' would accept. + A list of, or a single, `defun', `defun*', `defmacro', or `defadvice' forms. -TYPE is one of: +The def* forms accepted are: - `defun' (uses `cl-letf') - `defun*' (uses `cl-labels'; allows recursive references), - `defmacro' (uses `cl-macrolet') - `defadvice' (uses `defadvice!' before BODY, then `undefadvice!' after) - -NAME, ARGLIST, and BODY are the same as `defun', `defun*', `defmacro', and -`defadvice!', respectively. - -\(fn ((TYPE NAME ARGLIST &rest BODY) ...) BODY...)" + (defun NAME (ARGS...) &rest BODY) + Defines a temporary function with `cl-letf' + (defun* NAME (ARGS...) &rest BODY) + Defines a temporary function with `cl-labels' (allows recursive + definitions). + (defmacro NAME (ARGS...) &rest BODY) + Uses `cl-macrolet'. + (defadvice FUNCTION WHERE ADVICE) + Uses `advice-add' (then `advice-remove' afterwards). + (defadvice FUNCTION (HOW LAMBDA-LIST &optional NAME DEPTH) &rest BODY) + Defines temporary advice with `define-advice'." (declare (indent defun)) (setq body (macroexp-progn body)) (when (memq (car bindings) '(defun defun* defmacro defadvice))