1

Suppose that we have two functions foo1 and foo2. I would like to write a third function foo3 in such a way that the first execution of foo3results in the execution of foo1, the second execution of foo3 results in the execution of foo2 and so on (i.e. the nth execution of foo3 results in the execution of foo1 if n is odd, and results in the execution of foo2 if n is even).

Is this possible?

3

You can create a state variable to keep track of which function you called most recently, and condition the call in the third function on its value:

(defun fn1 ()
  (message "Function 1"))

(defun fn2 ()
  (message "Function 2"))

(defvar fn1-p nil)

(defun fn3 ()
  (setf fn1-p (not fn1-p))
  (funcall (if fn1-p #'fn1 #'fn2)))

EDIT: as per @politza's suggestion, here's a way to do the toggle with a closure (compare the example in the manual):

(setf lexical-binding t)                ; need to create a closure
(let (toggle)
  (setf fn4 (lambda ()
              (setf toggle (not toggle))
              (funcall (if toggle #'fn1 #'fn2)))))
(funcall fn4)                           ; => "Function 1"
(funcall fn4)                           ; => "Function 2"
(funcall fn4)                           ; => "Function 1"
  • Using a closure would be another option, depending on the use-case. – politza Apr 28 '15 at 20:38
4

This answer is written to demonstrate the little-known ring data structure available in Emacs' built-ins:

(defun function-1 () (print "Function 1"))

(defun function-2 () (print "Function 2"))

(defun function-3 () (print "Function 3"))

(defvar functions (let ((result (make-ring 3)))
                    (ring-insert result 'function-1)
                    (ring-insert result 'function-2)
                    (ring-insert result 'function-3)
                    result))

(defun cycle-functions ()
  (funcall
   (ring-insert functions
                (ring-ref functions
                          (1- (ring-size functions))))))

(with-output-to-string
  (cl-loop repeat 10 do (cycle-functions)))
"
\"Function 1\"

\"Function 2\"

\"Function 3\"

\"Function 1\"

\"Function 2\"

\"Function 3\"

\"Function 1\"

\"Function 2\"

\"Function 3\"

\"Function 1\"
"

The difference between this and other answers is that here you have the data structure to manipulate, which means that you can program a change in the order of execution of functions, whereas in other suggested answers this isn't an option. Whether you need that or not, is, of course, up to you.

  • Thanks, what (with-output-to-string (cl-loop for i below 10 do (cycle-functions)))... actually does? – Name Apr 29 '15 at 4:46
  • @Name with-output-to-strintg is a macro which binds the *standard-output* name buffer to a special buffer it creates, so that all text that you output using print and similar commands goes into that buffer. Then it returns the contents of that buffer. (cl-loop ...) simply calls the cycle-functions several times to generate some text printed from the functions it cycles through. – wvxvw Apr 29 '15 at 6:34
3

In this instance I think I'd forgo using a separate variable or a lexical closure, and just use a property on the function symbol to track its own state.

Here's Dan's answer adapted to that approach:

(defun fn1 ()
  (message "Function 1"))

(defun fn2 ()
  (message "Function 2"))

(defun fn3 ()
  (put 'fn3 'fn1-p (not (get 'fn3 'fn1-p)))
  (funcall (if (get 'fn3 'fn1-p) #'fn1 #'fn2)))
  • Nice! Hadn't realized you could do that. Tidier this way. – Dan Apr 28 '15 at 21:35
  • This would also depend on the use-case, of course. A variable may be more sensible for whatever the actual scenario is. This approach can be useful to know about, though. – phils Apr 28 '15 at 21:46
1

FWIW, here's the kind of way this kind of thing is usually done in existing Elisp packages (tho for repeated interactive execution of a command rather than just for repeated calls to a function):

(defun foo ()
  (interactive)
  (if (eq this-command last-command)
      (progn
        (setq this-command 'foo2)
        <foo2-behavior>)
    <foo1-behavior>))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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