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?

Student
  • 225
  • 1
  • 7

3 Answers3

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.

sds
  • 5,928
  • 20
  • 39
  • Another possibility is to advise `counsel-fzf` to change its behavior. See [chapter 13.11 Advising Emacs Lisp Functions](https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html) of the Emacs Lisp manual. – db48x Dec 23 '21 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 '21 at 20:59
  • @Student: that's different: now `fff` is in scope. – sds Dec 23 '21 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.

phils
  • 48,657
  • 3
  • 76
  • 115
  • 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 '21 at 00: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.
Student
  • 225
  • 1
  • 7