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.
    – db48x
    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!".
    – Student
    Dec 23, 2021 at 20:59
  • @Student: that's different: now fff is in scope.
    – sds
    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.
    – Student
    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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.