[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: elisp macros problem

From: Pascal Bourguignon
Subject: Re: elisp macros problem
Date: 27 Jul 2004 14:05:58 +0200
User-agent: Gnus/5.09 (Gnus v5.9.0) Emacs/21.3

Lowell Kirsh <address@hidden> writes:

> Hmmm, I was only trying to use macros because I thought what I was
> trying wasn't possible with functions. Would this be a more
> appropriate approach?
> (defun my-add-hook (hook &rest body)
>    (add-hook (intern (concat (symbol-name hook) "-mode-hook"))
>              `(lambda () ,@body)))
> and then call it like:
> (my-add-hook 'lisp-interaction
>               '(my-translate-paren-types)
>               '(imenu-add-to-menubar "Symbols")
>               )
> This seems a little more verbose with all the quotes, but I guess it
> could be the 'better' approach since it avoids using macros.
> Perhaps my macro approach was a crock. The only reason I defended it
> was becuase I thought you meant that creating the my-add-hook
> functionality itself was the crock.

There is no fundamental difference between a function and a macro.
The only difference, is that functions are called at so called
"run-time" while macros are called at so called "compilation-time".

Now, think about it, when you're using actually a lisp interpreter,
when is "run-time", and when is "compilation-time"?  (You can check
CLHS to see how Common-Lisp  defines these "times").

[There's also the little difference that macro lambda list may involve
destructuring of arguments, while function lambda list don't and
function must do it explicitely with DESTRUCTURE-BIND. But let's
ignore this detail for now.  In lisp that are lexically scoped like
Common-Lisp, there's also that little impact, that scopes can be
modified only at "compilation-time", not anymore at "run-time". So
you'd HAVE to use a macro to process scopes and lexical bindings as
first class objects in lexically scoped lisps.  But since emacs lisp
is not lexically scoped, this difference does not even matter.]

Let's consider a simple macro:

(defmacro myif (condition then &optional else)
   `(cond (,condition ,then)
          (t          ,else)))

(macroexpand-1 '(myif (= a b) 0 (- a b))) --> (COND ((= A B) 0) (T (- A B))) ; T

To see how one can  generate an equivalent function, let's use this

(defmacro functionify-macro (defmacro-sexp) 
  ;; Naive implementation, for pedagogical purposes only.
  ;; Lacks smart handling of lambda lists...
  (let ((fun (intern (concatenate 'string 
                       (string (second defmacro-sexp)) "-FUN"))))
       (defun ,fun
         ,@(cddr defmacro-sexp))
       (defmacro ,(second defmacro-sexp) ,(third defmacro-sexp)
         (,fun ,@(mapcan (lambda (arg) 
                            ((listp arg) (list (first arg)))
                            ((char= (character "&") (aref (string arg) 0))
                            ((symbolp arg) (list arg))
                            (t (error "Bad argument"))))
                         (third defmacro-sexp)))))))

(macroexpand-1 '(functionify-macro
                 (defmacro if (condition then &optional else)
                   `(cond (,condition ,then)
                          (t          ,else)))))

--> (PROGN

So, the macro just calls the function with exactly the same arguments.
Well, "exactly", not exactly, because if that little difference
between "compilation-time" and "run-time", when you invoke a macro,
the arguments are not evalued, but when you call a function they are.
Only that since the macro already has the arguments unevalued, what
they are are literal sexps, not mere values, so when the macro calls
the function, the arguments evaluate (at "compilation-time", remember
the macro executes at "compilation-time", to themselves, and the
function (called by the macro) executed at "compilation-time" will get
literal sexps as arguments.  That's why it'll be able to do the exact
same job as the macro;

 (defmacro myif (condition then &optional else)
   `(cond (,condition ,then)
          (t          ,else))))

(macroexpand-1 '(myif (= a b) 0 (- a b))) --> (COND ((= A B) 0) (T (- A B))) ; T
(myif-fun '(= a b) '0 '(- a b))           --> (COND ((= A B) 0) (T (- A B)))

Macros are more difficult to use "programmatically", that is, if you
don't know the exact value of the arguments at "compilation-time",
it's harder to use a macro (you'd have to use eval and have other
difficulties). That's why often you find that a defmacro is just a
thin layer over a function, so you can use the macro at
"compilation-time", and the function at "run-time". [See also the
difference between CLOS (defclass, defmethod MACROS) and MOP
(add-direct-subclass, add-method FUNCTIONS)].

Now, to come back to your problem,

 ;; a function:
 (add-hook-to-mode 'test  
    '(message "test mode hook")
    '(message "first hook"))


  ;; a macro:
  (add-hook-to-mode test 
     (message "test mode hook")
     (message "first hook"))

I don't see the point, compared to: 

    (add-hook 'test-mode-hook
        (lambda () 
            (message "test mode hook")
            (message "first hook")))

Anyway, if you insist on your function, you can always use progn to
minimize the number of quotes:

  (add-hook-to-mode 'test 
     '(progn (message "test mode hook")
             (message "first hook")))

> Well, I'm going with functions now, and I'm also going to get back to
> reading Paul Graham's 'On Lisp', the only comprehensive lisp macros
> book I know of (are there others?)

Beware that when you have a hammer in the hand, 
everything looks like a nail!

__Pascal Bourguignon__           
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?

reply via email to

[Prev in Thread] Current Thread [Next in Thread]