Endless Parentheses

Ramblings on productivity and technical subjects.

profile for Malabarba on Stack Exchange

Understanding letf and how it replaces flet

Common-Lisp in Emacs post series

Once you've come to terms with power of setf, it is time to meet its older sister, cl-letf. As the name implies, letf is to setf like let is to setq, but, once again, that is only the tip of the iceberg.

To get started, let's have a variable and require the feature.

(require 'cl-lib)
(setq my-list '(0 1 2 3 4 5))

When temporarily assigning a value to a variable, letf is (mostly) identical to let.

(cl-letf ((my-list "not actually a list"))
  (message "%s" my-list)) ;; ==> "not actually a list"

They differs slightly in behaviour when you don't provide a value. let binds the variable to nil and restores it upon exit (as I'm sure you know), while letf preserves the current value and restores it upon exit.

(cl-letf ((my-list))
  (message "%s" my-list)) ;; ==> "(0 1 2 3 4 5)"

(let ((my-list))
  (message "%s" my-list)) ;; ==> "nil"

Of course, that's not where letf shines. It's when you use place expressions that it simply blows let out of the water. Let's take a simple example using our list.

(cl-letf (((car my-list) 1000)
          ((elt my-list 3) 200))
  (message "%s" my-list)) ;; ==> "(1000 1 2 200 4 5)"

If you're having trouble reading past the three consecutive parentheses, this snippet simply takes our list and temporarily changes its first and fourth elements to 1000 and 200, respectively.

“Alright, but how often is that actually useful?”

It all depends on your creativity. This stackoverflow question highlights a common issue that came up with the release of Emacs 24.3. flet (short for function-let) was a macro which locally (and dynamically) replaced the function definition associated to given symbols. This is extremely useful for error testing, but if you try to use this macro now you'll get following the message.

Warning: `flet' is an obsolete macro (as of 24.3); use either `cl-flet' or `cl-letf'.

Unfortunately, cl-flet is not identical to the original flet—it's lexical, not dynamic.

For instance, the url-retrieve-synchronously usually prints a message on the echo area. Now say you want to prevent that, you can temporarily rebind the message function to do nothing.

(defun silent-retrieve ()
  "Example"
  (interactive)
  (flet ((message (&rest args) nil))
    (url-retrieve-synchronously
     "http://www.google.com")))

Now do M-x silent-retrieve, and see that it works. Unfortunately, if you try to be a good coder and replace the obsolete flet with the recommended cl-flet, it won't work!

letf as a replacement for flet

The solution I usually see floating around is to employ Nic Ferrier's fantastic noflet package. But I've never seen anyone mention the built-in option, even though Emacs itself tells you to use it: “use either `cl-flet' or `cl-letf'”.

You see, (symbol-function SYMBOL) is a valid place expression. Which means you can bind it dynamically using cl-letf. Evaluate the following defun and call M-x new-silent-retrieve.

(defun new-silent-retrieve ()
  "Example"
  (interactive)
  (cl-letf (((symbol-function 'message) #'format))
    (url-retrieve-synchronously 
     "http://www.google.com")))

It works! No messaging! All that we've done was tell letf to temporarily replace the message function with the format function (which does the same thing without echoing). We could also use ignore instead of format (the latter just happens to have the same return value as message).

Update

  • Wiesner reminds me of ignore.
  • Ferrier points out that noflet's this-fn feature is something you can't do with cl-letf.
  • Hodique teaches that Emacs 24.3.1 has a subtle bug in that regard.

Tags: lisp, emacs,

comments powered by Disqus