0

Suppose I have the function

(defun bar (a &optional (b nil bp))
  (declare (ignore a b bp)) ; <-- replace it with (list a b bp)
                            ;     to check for correctness
  )
;; I want to preserve the value of bp here inside bar, and inside foo below

and I wish to write a wrapper around bar:

(defun foo (a &optional (b nil bp))
  (declare (optimize speed))
  (apply #'bar a (nconc (when bp (list b)))))

while preserving the value of bp in the call from foo to bar, and also keeping the arguments b visible in the emacs' minibuffer / eldoc-mode. I wanted to know if there's a possibly-nonportable non-consing way to preserve the value of bp in this call.

For example,

CL-USER> (time (loop repeat 1000000 do (foo 4 2)))
Evaluation took:
  0.040 seconds of real time
  0.043656 seconds of total run time (0.043656 user, 0.000000 system)
  110.00% CPU
  96,380,086 processor cycles
  15,990,784 bytes consed <--- plenty of consing
  
NIL

If I ignore the b-visibility-in-eldoc, I could possibly use &rest arguments, but I do want the arguments to be visible.

While there are other ways to achieve this in this particular case, I do want to consider the case when there are multiple &optional (or &keyword) arguments.

2
  • Why are you using nconc? Just write (apply #'bar a (when bp (list b)))). Commented Apr 4, 2021 at 19:46
  • @adabsurdum In this particular example, nconc has no relevance, yes; when I wrote it I intended it for the case when there could be multiple optional arguments, say another (c nil cp) Commented Apr 4, 2021 at 19:52

2 Answers 2

3

Use a cond form to decide how to call bar:

(defun bar (a &optional (b nil bp) (c nil cp))
  (declare (ignore a b bp c cp)))

(defun foo (a &optional (b nil bp) (c nil cp))
  (declare (optimize speed))
  (cond (cp (funcall #'bar a b c))
        (bp (funcall #'bar a b))
        (t (funcall #'bar a))))
CL-USER> (time (loop repeat 1000000 do (foo 1 2 3)))
Evaluation took:
  0.015 seconds of real time
  0.017203 seconds of total run time (0.017148 user, 0.000055 system)
  113.33% CPU
  41,186,554 processor cycles
  0 bytes consed

Checking the arguments passed to bar:

(defun bar (a &optional (b nil bp) (c nil cp))
  (list a b bp c cp))
CL-USER> (foo 1 2 3)
(1 2 T 3 T)
CL-USER> (foo 1 2)
(1 2 T NIL NIL)
CL-USER> (foo 1)
(1 NIL NIL NIL NIL)
Sign up to request clarification or add additional context in comments.

8 Comments

To think of it, using cond seems to be the best way to go about handling &optional arguments. Any other method (including using nconc with list) seems to require at least a linear number of steps to do any "set up". I'll wait a while before accepting this.
Is there an equivalent version for &key arguments? Supplying an &rest parameter can help avoid the consing, but changes the argument list displayed by slime/eldoc. OTOH, using cond would result in exponentially many branches.
The other alternative is to use compiler-macros, which is what I have been doing, but I wanted to know if there are other ways to achieve this.
The &optional and &key don't sit very well together, but for &key arguments alone you would need to test for each combination; I don't see any other way. That wouldn't be a big deal for small numbers of keyword arguments. You could probably write some macros to help you out there for larger numbers of arguments.
@digikar (... &rest args &key a b c) combined with suitable ignore declarations & (apply ... args) is the canonical approach for keywords. If whatever tool is not smart enough to realise that the function takes exactly a fixed set of keywords in that case it needs to be made smarter: there's no purpose in deforming the program beyond recognition to help the tool limp along.
|
-1

I am not sure what bar and foo are actually supposed to do but what about:

(defun foo (a &optional (b nil bp))
  (declare (optimize speed))
  (funcall #'bar a (when bp b))) 

CL-USER> (time (loop repeat 1000000 do (foo 4 2)))
Evaluation took:
  0.005 seconds of real time
  0.005810 seconds of total run time (0.005804 user, 0.000006 system)
  120.00% CPU
  8,099,624 processor cycles
  0 bytes consed

2 Comments

This does not preserve the value of bp inside bar. The funcall version yields the value of bp as t, while the apply version yields nil when b is not supplied.
Hmmm, I see. I was just focused on the use of list inside your wrapper function which obviously causes the consing.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.