How can I define a &key argument that supersedes an &optional parameter in Lisp
Asked Answered
P

4

5

I just started writing this function and I was wondering if there was a way, that if just the &key argument was entered, the &optional list could be ignored.

(defun test (&optional arg (i 0) &key (size s))
  ...)

I would like to be able to run

(test arg)

or

(test arg i)

but also

(test :size)

Now this is a better mock up but I don't know where to put :size in params list

    (defun test (&optional arg (i 0))
      (cond ((eq arg nil) (return-from test (test-1)))
        ((listp arg)
         (return-from test (test-2 arg)))
        ((pointerp arg) (mem-aref (test-3 arg) :int i))
            (:size (size-test arg))
        (t nil)))

    so i can run (test) and get:

    <output of (test-1)>


    I can run (test '(1 2 3)) and get:


    <output of (test-2 arg)>


    I can run (test <pointer> 0)

    and output is:

    <output of (mem-aref (test-3 arg) :int i)>

    I can run (test :size) and get:

    <output of (test-size arg)>
Periapt answered 21/4, 2014 at 16:43 Comment(2)
Thank you for updating with some examples of what you'd like to do, but what is the intended meaning of (test :size)? When you provide arg and i, then :size has to be used with a value, e.g., (test arg i :size size). I'd understand wanting to use the keyword arguments without providing all the optional arguments, e.g., (test :size size), (test arg :size size), but I'm not sure what you'd have (test :size). Implementing what you want won't be all that difficult, but you'll need to specify it more concretely first.Superfetation
Based on your description, a simpler way to write this might be to just use (defun test (arg i &key size) ...) with the documentation: "arg—either a list or the symbol :size. If arg is a list, then if the empty list …, else if a non-empty list …. If arg is the symbol :size, then … and the other arguments are ignored."Superfetation
P
0

Posting as answer because I solved the main issue here is the resultant code I came up with, The cond statements are what matter. The &args usage created another issue and that post is being discussed here. The ((symbolp (cadr args)) (%vector-float-size (first args))) line is what I came up with from Joshua Taylors kindly written and extremely informative answer.

(defun vector-float (&rest args)
  (cond ((eq (first args) nil) (return-from vector-float (%vector-float)))
    ((listp (first args))
     (c-arr-to-vector-float (first args)))
    ((symbolp (cadr args)) (%vector-float-size (first args)))
    ((pointerp (first args)) (mem-aref (%vector-float-to-c-array (first args)) :float (second args)))
    (t nil)))
Periapt answered 26/4, 2014 at 4:50 Comment(0)
S
9

Mixing optional and keyword arguments

Mixing optional and keyword arguments is still something that's not all that easy to do. If a function accepts an optional argument, then you won't be able to use the keyword arguments unless the optional argument is also provided. Otherwise, the first keyword would be interpreted as the optional argument, and so on. See, for instance, this Stack Overflow question: How can I have optional arguments AND keyword arguments to the same function?. As the answer to that question points out, it's usually a bug-prone practice to mix optional and keyword arguments. Common Lisp does it with read-from-string, and it often leads people into trouble.

What you're proposing, though, isn't just having a function that uses both keyword and optional arguments, but, from the sounds of it, is actually doing some checking of the types of arguments, and taking one behavior in one case, and another in another. In this case, if i is supposed to be a number, then you could check the first argument, and if it's a number, then treat it as the optional argument, and the rest as keyword arguments, and if it's not a number, then treat the whole list as keyword arguments. You can do that with an &rest argument that you destructure in different ways:

(defun frob (&rest args)
  (flet ((frob-driver (i size)
           (list i size)))
    (if (or (endp args) (numberp (first args)))
        ;; no args, or the first argument is a number (and thus
        ;; not a keyword argument)...
        (destructuring-bind (&optional (i 'default-i) &key (size 'default-size)) args
          (frob-driver i size))
        ;; otherwise, there are some non-numeric arguments at 
        ;; beginning, so it must be the keyword list, and that the
        ;; "optional" wasn't provided.
        (destructuring-bind (&key (size 'default-size) &aux (i 'default-i)) args
          (frob-driver i size)))))
(frob 10 :size 50)             ; give i and size
;=> (10 50)

(frob :size 60)                ; give size, but not i
;=> (default-i 60)

(frob 40)                      ; give i, but not size
;=> (40 default-size)

(frob)                         ; give neither
;=> (default-i default-size)

Keyword arguments without keyword symbols

In the comments, you mentioned that you'd like to be able to use non-keyword symbols as keywords in argument lists. This is easy enough. In the HyperSpec, §3.4.1 Ordinary Lambda Lists describes the syntax for keyword arguments:

[&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]] 

This means that you can define functions like this:

(defun frob (&key foo ((bar-keyword bar-variable) 'default-baz))
  (list foo bar-variable))
(frob :foo 1 'bar-keyword 2)
;=> (1 2)

(frob :foo 3)
;=> (3 default-baz)

(frob 'bar-keyword 2)
;=> (nil 2)
Superfetation answered 21/4, 2014 at 19:14 Comment(4)
thanks for your great answer so far but I was hoping when I called (test :size) to run a specific function, without adding anything after :size like (test :size 0)....sorry I should have been clearer...but I would like :size to be a keyword so it's colored....I've learned alot so far thoughPeriapt
@Periapt OK, then you might need to do a bit more complicated checking of the argument list (e.g., checking it's length, etc.). If you can update the question to show all the permissible ways that you want the function to be callable, I bet we can produce a way to do it.Superfetation
thank you very much for your attention to this matter...I wrote an edit as you requestedPeriapt
This was a great response! Thank you! :DOthilie
C
7

You have to use a &rest argument list and process it in the function.

Mixing optional and keyword arguments should be avoided. It is BAD style to use optional and keyword arguments. This has been the source for countless errors with the few functions which use that (like READ-FROM-STRING).

Cranial answered 21/4, 2014 at 17:53 Comment(1)
Thank you :), that is closer, but in in a &rest args list how would I define my (i 0), that has to be there, for surePeriapt
M
0

In short, "no". There's no way of making that happen. As a general rule, try to stay away from mixing &rest, &optional and &key arguments, since their interactions are subtle and will more frequently trip you up than actually be useful.

Furthermore, if :size is a keyword argument, hen (test :size) is missing one argument (the value to bind size to). Your best bet is probably to look a arg to see if it is :size or something else.

Mud answered 21/4, 2014 at 17:2 Comment(4)
Thanks for your reply:), I was wondering is there any way to do it with a symbol, rather than a keyword, or anyway at all?Periapt
@Periapt Keywords are symbols. There's nothing special about keyword symbols in doing keyword arguments, except that they're the default. You can use any symbol as keyword argument, e.g., to do (foo my-package::a 1 your-package::b 2). In Ordinary Lambda Lists you can see that the full syntax for specifying keyword arguments is [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]] .Superfetation
@Joshua Taylor..How would I get started inventing a way to do this, it would really pretty up my code, Thanks for joining in btw:)Periapt
@Periapt I've added an answer.Superfetation
P
0

Posting as answer because I solved the main issue here is the resultant code I came up with, The cond statements are what matter. The &args usage created another issue and that post is being discussed here. The ((symbolp (cadr args)) (%vector-float-size (first args))) line is what I came up with from Joshua Taylors kindly written and extremely informative answer.

(defun vector-float (&rest args)
  (cond ((eq (first args) nil) (return-from vector-float (%vector-float)))
    ((listp (first args))
     (c-arr-to-vector-float (first args)))
    ((symbolp (cadr args)) (%vector-float-size (first args)))
    ((pointerp (first args)) (mem-aref (%vector-float-to-c-array (first args)) :float (second args)))
    (t nil)))
Periapt answered 26/4, 2014 at 4:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.