0

I want to slightly alter the behavior of counsel-fzf by locally changing the definition of counsel-fzf-action, which activates in the end of the definition of the former. See the source code of counsel-fzf (please pay attention to the second last line):

(defun counsel-fzf (&optional initial-input initial-directory fzf-prompt) (interactive (let ((fzf-basename (car (split-string counsel-fzf-cmd)))) (list nil (when current-prefix-arg (counsel-read-directory-name (concat fzf-basename " in directory: ")))))) (counsel-require-program counsel-fzf-cmd) (setq counsel--fzf-dir (or initial-directory (funcall counsel-fzf-dir-function))) (ivy-read (or fzf-prompt "fzf: ") #'counsel-fzf-function :initial-input initial-input :re-builder #'ivy--regex-fuzzy :dynamic-collection t :action #'counsel-fzf-action ; <=== This is what I'll alter :caller 'counsel-fzf)) 

To slightly alter it, I tried nulling #'counsel-fzf-action locally by cl-flet.

(cl-flet ((counsel-fzf-action (x) nil)) (counsel-fzf)) 

Question

However, the global function counsel-fzf-action is executed instead of the local one. This confuses me because shouldn't the local function gets favored first?

1

3 Answers 3

2

You are out of luck here; this is the correct intended behavior.

#'counsel-fzf-action refers to the global function definition of counsel-fzf-action, not the local one.

The cl-flet binding is local, similar to the lexical binding of let: unless the variable being bound is dynamic (i.e., defined using defvar), the nested functions do not see the binding.

Thus the only way out for you is to modify counsel-fzf to either accept the action argument or use a global defvar variable instead of #'counsel-fzf-action.

3
  • Another possibility is to advise counsel-fzf to change its behavior. See chapter 13.11 Advising Emacs Lisp Functions of the Emacs Lisp manual. Commented Dec 23, 2021 at 19:04
  • @sds I'm still confused. Normally, #'XYZ definitely should refer to the local function definition if any, instead of the global one. See for example: (progn (defun fff () "global!") (funcall (cl-flet ((fff () "local!")) #'fff))) which returns "local!". Commented Dec 23, 2021 at 20:59
  • @Student: that's different: now fff is in scope. Commented Dec 23, 2021 at 21:09
1

There are many options for overriding functions, with many different behaviours. https://stackoverflow.com/questions/39550578/in-emacs-what-is-the-difference-between-cl-flet-and-cl-letf will probably resolve the confusion for you.

The first thing to know is that cl-flet is not the same thing as flet!

flet was dynamically scoped (and hence would have worked the way you expected), whereas cl-flet (and cl-labels) are lexically-scoped and won't.

The new way to mimic flet is to use cl-letf with a PLACE of (symbol-function 'FUNC)

See the linked Q&A for examples.

1
  • This makes sense! I did more research and wrote up a complete answer below. Please let me know if there's any misunderstanding. Thank you. Commented Dec 24, 2021 at 0:51
0

I want to thank phils and sds for providing their answers. After learning from them and a bit more research, I have come to an understanding mostly from an excellently written article, Make flet great again (by Chris Wellons).

The article explained that cl-flet is lexically scoped (as in flet in common lisp), and therefore in my use case

(cl-flet ((counsel-fzf-action (x) nil)) (counsel-fzf)) 

the inner definition of (counsel-fzf) does not see the change of #'counsel-fzf-action. What I intended to use, is the dynamically scoped flet (but long deprecated in elisp). And indeed that's what I wanted. However, flet is deprecated and should not be used. No worries! The article also provides a solution, and that's by using cl-letf (also mentioned by phils), where f means "form" (as in the f in setf). So for what I intended, I should use the following

(cl-letf (((symbol-function 'counsel-fzf-action) (lambda (x) nil))) (funcall #'counsel-fzf)) 

Summary

Currently in elisp (emacs 27.2),

  • cl-flet is lexically scoped (as in the flet in common lisp), and behaves more sanely for byte compiled functions.
  • cl-letf is dynamically scoped.
  • flet is deprecated and should not be used. Instead one should use cl-letf if they need.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.