Programmatical Function Definition: How to get rid of "eval" here?
Asked Answered
E

2

5

I have a set of functions named "ip", "date", "url" etc.

With these, I want to generate another set of functions "ip-is", "date-is" etc.

I finally have the following solution, thats working fine, but that uses "eval".

(loop for name in '(ip date url code bytes referer user-agent) do
  (let ((c-name (intern (concatenate 'string (symbol-name name) "-IS"))))
    (eval `(defun ,c-name (c)
           #'(lambda (l) (equal (,name l) c))))))

Can someone help me, how to get rid of the "evil eval"? It is essential for my program that the function names are provided as a list. So a call to some marcro

   (define-predicate ip)
   (define-predicate date)
   (define-predicate url)

etc.

would not fit my needs. I have no real problem with "eval", but I read very often, that eval is considered bad style and should be avoided if possible.

Thanks in Advance!

Extenuate answered 10/2, 2013 at 14:49 Comment(0)
A
7

You should use a macro here. Macros are evaluated during compile (or load) and can be used to programatically generate a function definition. Your code could be written something like this:

(defmacro define-predicates (&rest names)
  `(progn
     ,@(loop
          for name in names
          collect (let ((c-sym (gensym))
                        (l-sym (gensym)))
                    `(defun ,(intern (concatenate 'string (symbol-name name) "-IS")) (,c-sym)
                       #'(lambda (,l-sym) (equal (,name ,l-sym) ,c-sym)))))))


(define-predicates ip date url)

Note that the symbols are generated using GENSYM in the functions. In this particular case, that's not strictly necessary, but I usually prefer to do it this way just so that there is no chance of having any leaking if I were to refactor the code at a later stage.

Angrist answered 10/2, 2013 at 16:34 Comment(2)
Definitely the right case for macros. Using eval as the right choice is a very rare case.Tuesday
@Elias, just a question about what you wrote: it seems to me macros can't be evaluated at load time since they are used at compile time to generate code.Illdefined
T
5

If you want to use a function (instead of a macro as in the other answer), you should be using (setf fdefinition):

(loop for name in '(ip date url code bytes referer user-agent) do
  (let ((c-name (intern (concatenate 'string (symbol-name name) "-IS"))))
    (setf (fdefinition c-name)
          (lambda (c) (lambda (l) (equal (funcall name l) c))))))
Tubman answered 10/2, 2013 at 18:27 Comment(5)
Hi, this is exactly, what I am looking for, but for some reason, it does not work for me. I understand the solution but I could not figure out, why its not working. I am using sbcl 1.0.58Extenuate
it compiles and if i call (code-is "200") it gives me a predicate closure. So long everything is fine. But if I call it with my filter routine, it does not match the case correctly when the function "code" delivers "200". If I call it directy, eg. (defun code (z) "200"), then run your definition, then (funcall (code-is "200") "200") it breaks with "INVALID-ARRAY-INDEX-ERROR". Thats rather strange, since we are not dealing with arrays here.Extenuate
@Patrick: just tested it with sbcl 1.0.55.0; works just fine; (funcall (code-is "200") "200") returns T.Tubman
I've usually seen 'symbol-function used in this case instead of 'fdefinition. Looking at Clozure's src, the only difference is that fdefinition allows its argument to evaluate to a non symbol. Not sure what that use case is exactly. So 'symbol-function would work in this case, and in all cases that I can think of. Let me know if you can think of a use case where the arg to fdefinition is not a symbol.Fredrika
@ClaytonStanley: (fdefinition '(setf foo)) is the only non-symbol.Tubman

© 2022 - 2024 — McMap. All rights reserved.