[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: hypothetical question about macros
From: |
Tim X |
Subject: |
Re: hypothetical question about macros |
Date: |
Tue, 24 May 2011 20:00:58 -0000 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/24.0.50 (gnu/linux) |
Alan <wehmann@fnal.gov> writes:
> Here is a hypothetical question about macros. Since a macro can
> result in code that gets interpreted, suppose one wanted to have a
> macro insert the following code:
I don't think this is the major point regarding macros. From that
perspective, a macro is no different to a function in the sense they
both result in code that gets interpreted. For me, the distinction, at
least partially, is the different time at which macros get evaluated
compared to functions and that the arguments to a macro are not
evaluated as is the case with functions. This means you can add new
elisp control structures and other programming constructs to extend the
language etc.
>
> (insert "#1\n")
> (insert "#2\n")
> (insert "#3\n")
> (insert "#4\n")
>
> This is a silly example, since in real life one is rather unlikely to
> do such a thing. However, it is an exercise of interest for
> understanding macros.
>
> Reading the documentation and making some trials, I don't see how to
> do this. For example
>
> (defmacro aw-test ()
> '(insert "#1\n")
> '(insert "#2\n")
> '(insert "#3\n")
> '(insert "#4\n"))
>
Remember that like functions, macros return a 'value' and the default is
to return the value returned by the evaluation of the last form.
Essentially, the first 3 forms are evaluated and return (insert ...),
but that return value is ignored/thrown away. Only the last one is
returned as the result of the macro execution and then evaluated.
> and then using "eval-print-last-sexp" in the scratch buffer on
>
> (aw-test)
>
> results in
>
> #4
> nil
>
Yep, that is correct.
> Similarly:
>
> (macroexpand '(aw-test))
>
> results in
>
> (insert "#4
> ")
>
Yep, because only the result of the last statement evaluated is returned.
In this case, it is evaluating the value of (quote (insert "#4\n")),
which results in (insert "#4\n"), which is what the macro returns and is
subsequently evaluated to insert #4\n into the buffer.
> The following is a possibility:
>
> (defmacro aw-test ()
> '(progn (insert "#1\n")
> (insert "#2\n")
> (insert "#3\n")
> (insert "#4\n")))
>
> In the scratch buffer, using "eval-print-last-sexp" on
>
> (aw-test)
>
> then gives
>
> #1
> #2
> #3
> #4
> nil
>
> and
>
> (macroexpand '(aw-test))
>
> results in:
>
> (progn (insert "#1
> ") (insert "#2
> ") (insert "#3
> ") (insert "#4
> "))
>
> But I had to resort to using "progn" in this case.
>
> If there were some reason not to have to resort to using "progn" I
> don't see how to do so.
Consider what it is you are trying to do. You want the macro to return
some elisp code that when evaluated, will give your desired result. Your
desired result is to evaluate multiple forms. However, a macro only
returns a single form. Using progn is one way of doing it, but think
about how lisp code is structure and you can see alternative ways,
though in this case, I think progn is perfectly acceptable.
One way to think about macros is to work backwards rather than forwards.
Start with the bit of code you want to have evaluated and then look at
how that could be returned by the macro, then look at how to generate
that structure within the macro and then look at how to add any dynamic
bits (i.e. arguments etc passed as part of the macro call).
Perhaps the benefits of macros cannot really be seen with the example
your using as you could achieve the same outcome easily with a normal
function. Here is a copy of the very first macro I ever wrote. It was to
make my debugging of code much easier. Note that this macro does have
some problems and limitations, but I think it demonstrates things with
less complexity than a more sophisticated version which avoids potential
pitfalls (for example, consider what impact a macro for debugging might
have if it evaluated arguments as part of the expansion and either that
evaluation changed the execution path of the code or the evaluation
resulted in a side effect that affected the execution path of the code
being debugged or what would happen with this macro if one of its
arguments was something like (+ 1 2) or even more likely, you want to
include some sort of evaluation such as (< a b) to aid in debugging).
(defmacro my-debug (file func &rest vals)
`(when *do-debug*
(message "File: %s Function: %s" ,file ,func)
(dolist (v ',vals)
(message "\t%s = %s" (symbol-name v) (symbol-value v)))))
and the way I would use it is something like
(setq *do-debug* t)
(defun my-func (arg1 arg2)
(let ((a "this is a")
(b "this is b"))
(do some stuff.....)
(my-debug "test.el" "my-func" arg1 arg2 a b)))
(my-func "this is arg1" "this is arg2")
To see why I used a macro rather than just a function, consider how you
could have a function where inside the function you can determine the
symbol names of the arguments used in the call so that you can write
something like
symbol's name = symbol's value
as part of your debug output. i.e. the output from the above would be
soemthing like
File: test.el Function: my-func
arg1 = this is arg1
arg2 = this is arg2
a = this is a
b = this is b
I personally found writing a macro to assist in my debugging a very
valuable learning experience. The macro I typically use now is more
complex, but handles things in a more general way. There are some things
I still don't quite like with it and continue to improve and refine (and
regularly break it). Nearly all the problems I've encountered with it
are due to my lack of understanding/insight and working these out has
really helped.
In reality, I rarely use macros in elisp. I have used them more often in
CL. I suspect this may be because, to some extent, elisp is a DSL
already and most of the higher level control structures or extensions
specific to things neded in an editor already exist. On the other hand
CL is a more general programming language and applied in a wider range
of domains, so you can easily find a situation where you need a new
control structure or notice common domain specific bits of code that
could be made clearer or reduce the level of boiler-plate code through
the addition of some domain specific macros.
In the end, macros are a really great and useful tool, but possibly used
a lot less than one would expect for something which seems so
powerful/useful. I only resort to a macro if what I want to do cannot be
done with a function.
Tim
--
tcross (at) rapttech dot com dot au