Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Odd length keyword list

89 views
Skip to first unread message

Matus Kmit

unread,
Jan 11, 2012, 10:45:12 AM1/11/12
to
Hi

I have this macro:

(defmacro format-time (one &optional two &key format-fn)
(let ((one* (eval one))
(two* (eval two)))
(if two*
`(,format-fn t "(~a:~a - ~a:~a)" ,@one* ,@two*)
`(,format-fn t "~a:~a" ,@one*))))

If I try to macroexpand it with (macroexpand '(format-time (normalize-
consed-time '(1 . 2)) :format-fn format)) I am getting the following
error:

Odd length keyword list: (FORMAT)
[Condition of type CCL::SIMPLE-PROGRAM-ERROR]

What is wrong?

Thanks
Matus

Tamas Papp

unread,
Jan 11, 2012, 11:02:50 AM1/11/12
to
Consider how

((normalize-consed-time '(1 . 2)) :format-fn format)

gets matched to arguments in the lambda list:

(one &optional two &key format-fn)

- ONE is NORMALIZE-CONSED-TIME,
- TWO is :FORMAT-FN,
- FORMAT stands in place of a keyword (but is not recognized as a valid one for this macro)
- finally, the value that corresponds to the keyword is missing.

Maybe you could make both arguments keyword parameters.

Best,

Tamas

Tamas Papp

unread,
Jan 11, 2012, 11:04:05 AM1/11/12
to
On Wed, 11 Jan 2012 16:02:50 +0000, Tamas Papp wrote:

> - ONE is NORMALIZE-CONSED-TIME,

Typo, rather

(NORMALIZE-CONSED-TIME '(1 . 2))

David Brown

unread,
Jan 11, 2012, 11:09:20 AM1/11/12
to
On 2012-01-11, Matus Kmit <simply...@gmail.com> wrote:

> (defmacro format-time (one &optional two &key format-fn)
> (let ((one* (eval one))
> (two* (eval two)))
> (if two*
> `(,format-fn t "(~a:~a - ~a:~a)" ,@one* ,@two*)
> `(,format-fn t "~a:~a" ,@one*))))
>
> If I try to macroexpand it with (macroexpand '(format-time (normalize-
> consed-time '(1 . 2)) :format-fn format)) I am getting the following
> error:
>
> Odd length keyword list: (FORMAT)
> [Condition of type CCL::SIMPLE-PROGRAM-ERROR]

If you have both optional and keyword arguments in a lambda list, the
caller will have to specify all of the optional arguments before any
keyword arguments. In other words, the :format-fn in your call is
being interpreted as the value for TWO, and then FORMAT is being
interpreted as the first keyword, which isn't paired with a value.

David

Pascal J. Bourguignon

unread,
Jan 11, 2012, 11:25:45 AM1/11/12
to
Notice that the keys are not necessarily keywords. They can be any
symbol.

(defun example (&key ((not-a-keyword parameter) nil parameter-present-p))
(list parameter-present-p parameter))

CL-USER> (example 'not-a-keyword 42)
(T 42)
CL-USER> (apply 'example '(not-a-keyword 42))
(T 42)



Remember that &key is really just a &rest coupled with some parsing of
the &rest list. When you have both &rest and &key, the &rest list
collects the &key arguments! &key imposes an even number of &rest
arguments, and if you want to have more arguments than what's possible
with &key, you need to &allow-other-keys too.


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.

kenny

unread,
Jan 11, 2012, 12:59:30 PM1/11/12
to
Answered elsewhere, I presume. Meanwhile, you are pretty much doomed
trying to make mix optional and keyword params. But macros give you a
little more control, so you might have done:

(defmacro format-time ((one &optional two) &key (format-fn 'format))

I threw in the default value for format-fn, which you really should
have.

Supermeanwhile: why is this a macro?

-hk

Tim Bradshaw

unread,
Jan 11, 2012, 1:21:22 PM1/11/12
to
On 2012-01-11 17:59:30 +0000, kenny said:

> Supermeanwhile

metameanwhile, surely? (this article, however, may be super-, but may
actually be hyper-)

Matus Kmit

unread,
Jan 12, 2012, 3:19:21 AM1/12/12
to
Thanks all for your help. I got it.

>
> Supermeanwhile: why is this a macro?
>
> -hk

More or less an experiment, while being bored at work and messing
aroung with lisp trying to understand macros, eval, parameter types,
etc...

On the other hand I was looking for a way how to splice parameters
which are given in a list inside the format function. The first thing
I could think of was macro, although a syntactic abstraction for this
particular case probably does not make much sense. Meanwhile :-), I
remembered "apply", which seems to do just fine what i need:

(apply #'format t (cons "~a:~a" (normalize-consed-time '(1 . 2))))

greetings,
Matus

kenny

unread,
Jan 12, 2012, 9:29:39 AM1/12/12
to
Kenny: This nail gun is great!
Matus: Cool! Hand it over, this fly has been bothering everyone in the
office...

Tilton's Law: Artificial experiments do not experiment.

hk

Matus Kmit

unread,
Jan 12, 2012, 11:05:30 AM1/12/12
to
Good point.

Raymond Wiker

unread,
Jan 12, 2012, 12:02:03 PM1/12/12
to
Another point, possibly more useful: you might want to use the backquote
mechanism on its own, instead of going the macro route...

Matus Kmit

unread,
Jan 12, 2012, 12:53:25 PM1/12/12
to
On 12 Jan., 18:02, Raymond Wiker <r...@unknown-00-23-6c-8d-9e-26.lan>
wrote:
Thanks for the tip, but even more useful was, if you'd give me an
example. Would do you exactly mean? Something like: (eval `(format t
"~a:~a" ,(list 1 2))) ?

Kaz Kylheku

unread,
Jan 12, 2012, 2:55:27 PM1/12/12
to
On 2012-01-11, Matus Kmit <simply...@gmail.com> wrote:
> Hi
>
> I have this macro:
>
> (defmacro format-time (one &optional two &key format-fn)
> (let ((one* (eval one))
> (two* (eval two)))
> (if two*
> `(,format-fn t "(~a:~a - ~a:~a)" ,@one* ,@two*)
> `(,format-fn t "~a:~a" ,@one*))))

You're trying to move a run-time problem to compile time. You have one
or two expressions that return a list of two items. But you would
like to pass these as two or four arguments, not one or two lists.

Those lists are not available at macro-expansion time, so this splicing
will do you no good, and the eval calls you have there are just trouble,
because they happen at macro-expansion time.

Firstly, the format language has gadgets for dealing with lists:

(format t "~gobbledygook" '(1 2) '(3 4))

Secondly, if you don't want to do that, you can solve this
with a simple function:

;; untested: exercise for reader
(defun format-time (one &optional two &key (format-fn #'format))
(if two
(funcall #'format-fn t "(~a:~a - ~a:a)"
(first one) (second one) (first two) (second two))
(funcall #'format-fn t "~a:~a" (first one) (second one))))

Functional abstraction is your first tool.

Based on this solution, you can develop a macro. But then you're
just doing a useless macro inlining of a function which just as well be be
achieved with (declaim (inline ....)).

(What is the reason for creating a macro? Do you need to control
the evaluation of forms, or interpret them forms other than
as Lisp expressions? Ah right, you want to specify the :function
argument as a simple symbol without quoting! That's why.)

So what might the macro solution look like?

Possibly along these lines:

;; untested: exercise for reader
(defmacro format-time (one &optional two &key (format-fn 'format))
(let ((one-sym (gensym))
(two-sym (gensym)))
(if two
`(let ((,one-sym ,one)
(,two-sym ,two))
(,format-fn t "(~a:~a - ~a:a)"
(first ,one-sym) (second ,one-sym)
(first ,tw-sym) (second ,two-sym))
`(let ((,one-sym ,one))
(,format-fn t "~a:~a" (first ,one-sym) (second ,one-sym)))))))

Note that we give the format-fn key argument a default value.
Most of the time you're going to use format, right? If the caller
forgets to specify the argument, you end up the expansion (nil ...)
which results in a mysterious error, like "no such function: nil".

Raymond Wiker

unread,
Jan 13, 2012, 1:43:53 AM1/13/12
to
More like (apply #'format `(t "~a:~a" ,@'(1 2)))

or even

(let ((what (list 1 2)))
(apply #'format `(t "~a:~a" ,@what)))

-- no need to use eval.

Matus Kmit

unread,
Jan 13, 2012, 5:17:04 AM1/13/12
to
On 12 Jan., 20:55, Kaz Kylheku <k...@kylheku.com> wrote:
Thanks all again. Always very valuable insights for me.
0 new messages