LISP: Some help writing a macro.

Started by
9 comments, last by GameDev.net 18 years, 9 months ago
I've created a macro called 'deffunc' that is supposed to add pre- and post-condition options to 'defun'. It works quite well, but it is flawed because of some potential symbol-name overlapping. (defmacro deffunc (name args code &key (pre t) (post t)) `(defun ,name ,args (if (not ,pre) (format t "Pre-condition on ~S failed!" ,(symbol-name name)) (let ((result ,code)) (if (not ,post) (format t "Post-condition on ~S failed!" ,(symbol-name name)) result)) ))) (deffunc testing (a) (* a 2) :pre (numberp a) :post (eql result (* a 2))) My only issue here is that I use the symbol "result" to equal the result of the function call, the :post predicate can access the value "result". However, I would rather use the keyword ":result" instead, but because keywords are self-evaluating, I can't just replace "result" for ":result" in my macro. I think I would need to somehow take the value of :post, and replace ":result" with another symbol, but I have no idea how to search through a list (:post), and replace symbols inside of it. Any ideas?
Advertisement
I've considered the idea of the user writing a lambda function as the post-condition, and sending :result as an argument to that lambda function, but that seems too awkward, I wanna keep this as straight-forward as possible for the end-user of the macro.
Searching through code and replacing bits of it isn't really a lispy thing to do. If I were writing this macro, I'd just let the user specify what the result should be called:

(defmacro deffunc (name args expr &key (pre t) (post t) (result 'result))
`(defun ,name ,args
(unless ,pre
(format *error-output* "~&Pre-condition on ~S failed!~%" ',name))
(let ((,result ,expr))
(unless ,post
(format *error-output* "~&Post-condition on ~S failed!~%" ',name))
,result)))

(deffunc testing (a)
(* a 2)
:pre (numberp a)
:result testing-result
:post (eql testing-result (* a 2)))


Since function names aren't necessarily symbols, calling SYMBOL-NAME on them isn't really the right thing either, so I've eliminated that (and sent the messages to non-*standard-output*; *debug-io* and *trace-output* might also be reasonable choices for the output streams).

This would also require some thinking for use with multiple return values.. for that case, I'd probably make it something like this:

(defmacro deffunc (name args expr &key (pre t) (post t) (result 'result))
(when (symbolp result) (setf result (list result)))
`(defun ,name ,args
(unless ,pre
(format *error-output* "~&Pre-condition on ~S failed!~%" ',name))
(multiple-value-bind ,result ,expr
(unless ,post
(format *error-output* "~&Post-condition on ~S failed!~%" ',name))
(values ,@result))))

(deffunc ensured-gethash (key table)
(gethash key table)
:pre (hash-table-p table)
:result (value was-present)
:post was-present)

(ensured-gethash 5 (make-hash-table))
Post-condition on ENSURED-GETHASH failed!

If RESULT is a symbol, only the primary value is bound (and returned), otherwise all the names in the list given are bound, made available to the postcondition test, and returned.
Thanks anon, I liked how you handled that.


Quote:However, I would rather use the keyword ":result" instead, but because keywords are self-evaluating, I can't just replace "result" for ":result" in my macro.


I really don't understand this. Why would you want to use a keyword to do something that keywords cannot do (be bound to values).

Quote:I think I would need to somehow take the value of :post, and replace ":result" with another symbol, but I have no idea how to search through a list (:post), and replace symbols inside of it. Any ideas?


The value of :post is a constant: the symbol :post . The value of :result is a constant: the symbol :result

You simply shouldn't use keywords in expressions, they aren't meant to do that. Your initial solution is pretty good, the anon's is even better.

Parsing a list and replacing elements is actually pretty easy:

(defun tree-replace (tree element new-element &key (eq 'eq))  (if (consp tree)      (cons (tree-replace (car tree) element new-element :eq eq)            (tree-replace (cdr tree) element new-element :eq eq))    (if (funcall eq element tree) new-element tree)))


Thereafter you'll probably want to use gensym in the macro to ensure that the symbol used to store the results of evaluating the code and used to replace :result is unique and doesn't conflict with other symbols in the :post condition. (the gensym function creates a unique symbol)
By the way, it's spelled Lisp :)
Quote: Parsing a list and replacing elements is actually pretty easy


Yes. Use SUBSTITUTE for sequences and SUBST for trees.
Well, thanks for all the help. It turns out I eventually found a way to write a macro which takes an arbitrary expression and replaces ':result' with 'result', but I changed my deffunc macro to just let the user choose the name of his result, like anon. suggested.

(defmacro replace-expression (from to expr)    (defun f (l)       (let (r)         (dolist (x l r)            (if (listp x) (setq r (append r (list (f x))))                          (if (eq from x) (setq r (append r (list `,to)))                                          (setq r (append r (list x))))))))        `(f ',expr))


As an aside, is it possible to have a lambda function be recursive? If so, how?

Also...
The only problem with the above macro is that it defines a function f which is available globally, I want the function to be seen by ONLY the macro, how do I do this?

Sorry I'm a bit a new to lisp and trying my best to understand macros.

[Edited by - Brobanx on July 25, 2005 5:47:44 AM]
Quote:Original post by Brobanx


As an aside, is it possible to have a lambda function be recursive? If so, how?


No, it isn't.

Quote:Original post by Brobanx
Also...
The only problem with the above macro is that it defines a function f which is available globally, I want the function to be seen by ONLY the macro, how do I do this?


check out flet and lables. Both are used to produce local functions, flets can be recursive labels can not.
Acutally it's the other way around. labels can be recursive, flets cannot. The following code is correct.

(defmacro replace-expression (from to expr)   (labels ((f (l)       (let (r)         (dolist (x l r)            (if (listp x) (setq r (append r (list (f x))))                          (if (eq from x) (setq r (append r (list to)))                                          (setq r (append r (list x)))))))))          (f `',expr)))


right, I should perhaps read what i write.. before I post :)

This topic is closed to new replies.

Advertisement