Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lisp: Macros vs Functions [duplicate]

In my quest to fully understand the so powerful lisp macros a question came to my mind. I know that a golden rule about macros is the one saying "Never use a macro when a function will do the work". However reading Chapter 9 - Practical: Building a Unit Test Framework - from the book Practical Common Lisp I was introduced to the below macro whose purpose was to get rid of the duplication of the test case expression, with its attendant risk of mislabeling of results.

;; Function defintion. 

(defun report-result (result form)
  (format t "~:[FAIL~;pass~] ... ~a~%" result form))

;; Macro Definition

(defmacro check (form)
  `(report-result ,form ',form))

OK, I understand its purpose but I could have done it using a function instead of a macro, for instance:

(setf unevaluated.form '(= 2 (+ 2 3)))

(defun my-func (unevaluated.form)
  (report-result (eval unevaluated.form) unevaluated.form))
  1. Is this only possible because the given macro is too simple ?
  2. Furthermore, is Lisp Macro System so powerful relatively its opponents due to the code itself - like control structures, functions, etc - is represented as a LIST ?
like image 635
utxeee Avatar asked Jul 21 '12 20:07

utxeee


3 Answers

But if it were a macro you, could have done:

(check (= 2 (+ 2 3)))

With a function, you have to do:

(check '(= 2 (+ 2 3)))

Also, with the macro the (= 2 (+ 2 3)) is actually compiled by the compiler, whereas with the function it's evaluated by the eval function, not necessarily the same thing.

Addenda:

Yes, it's just evaluating the function. Now what that means is dependent upon the implementation. Some can interpret it, others can compile and execute it. But the simple matter is that you don't know from system to system.

The null lexical environment that others are mentioning is also a big deal.

Consider:

(defun add3f (form)
  (eval `(+ 3 ,form)))

(demacro add3m (form)
  `(+ 3 ,form))

Then observe:

[28]> (add3m (+ 2 3))
8
[29]> (add3f '(+ 2 3))
8
[30]> (let ((x 2)) (add3m (+ x 3)))
8
[31]> (let ((x 2)) (add3f '(+ x 3)))

*** - EVAL: variable X has no value
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead of X.
STORE-VALUE    :R2      Input a new value for X.
ABORT          :R3      Abort main loop
Break 1 [32]> :a

That's really quite damning for most use cases. Since the eval has no lexical environment, it can not "see" the x from the enclosing let.

like image 163
Will Hartung Avatar answered Oct 16 '22 10:10

Will Hartung


The better substitution would be not with eval, which won't perform as expected for all cases (for example, it doesn't have access to the lexical environment), and is also overkill (see here: https://stackoverflow.com/a/2571549/977052), but something using anonymous functions, like this:

(defun check (fn)
  (report-result (funcall fn) (function-body fn)))

CL-USER> (check (lambda () (= 2 (+ 2 3))))

By the way, this is how such things are accomplished in Ruby (anonymous functions are called procs there).

But, as you see, it becomes somewhat less elegant (unless you add syntax sugar) and, there's actually a bigger problem: ther's no function-body function in Lisp (although there may be non-standard ways to get at it). Overall, as you see, for this particular task the alternative solutions are substantially worse, although in some cases such approach could work.

In general, though, if you want to do something with the source code of the expressions passed into the macro (and usually this is the primary reason of using macros), functions would not be sufficient.

like image 39
Vsevolod Dyomkin Avatar answered Oct 16 '22 10:10

Vsevolod Dyomkin


The report-result function needs both the source code and the result of the execution.

The macro CHECK provides both from a single source form.

If you put a bunch of check forms into the file, they are easily compiled using the usual process of compiling Lisp files. You'll get a compiled version of the checking code.

Using a function and EVAL (better use COMPILE) you would have deferred the source evaluation to a later time. It would also not be clear if it is interpreted or compiled. In case of compilation, you would then later get the compiler's checks.

like image 40
Rainer Joswig Avatar answered Oct 16 '22 09:10

Rainer Joswig