Page 1 of 1

Another question about Macro vs. Function

Posted: Sun Mar 11, 2012 4:47 am
by mabu77
Hi all,

while fiddling about the 99 Lisp Problems (P46, e.g. [1]) I ran into a situation where I was able to implement a simple solution based on a macro while I am failing getting it done by a function. This gives me a bad feeling and I would love to have someone more experienced comment on my solution. Is it appropriate to use a macro here and why is a function-based solution not working here?

If I try to adopte my macro solution to the next problem (P47, e.g. [2]) I ran into trouble. I thought I could define a rather simple recursive function (swap-operators) which swaps the operators from infix to prefix notation. (Assuming that only valid three-elemente logical expression will occur. I have studied Pascal's solution [2] but it is way to complex for my level right now and I wanted to use only things which I completely understand.) If I use this function in combination with a macro (table2) I end up in a situation where the logical expression is not evaluated itself, i.e. my truth tables always prints true because there is something different from 'fail.

What would be the right way to combine my function manipulating the expression and the simple macros which was working before?

Any comment is appreciated
Martin



In detail:

Code: Select all

(defmacro table (a b expression)
  "Prints the truth table of a given logical expression in two variables."
  `(loop :for ,a :in '(t t nil nil)
	 :for ,b :in '(t nil t nil)
	 :do (format t "~{~:[Fail~;True~]~^~T~}~%"
		     ;; A bit awkward but I wanted to use True/Fail instead of
		     ;; Lisp's t/nil but also wanted to use format's iteration
		     ;; and conditional printing feature.
		     (list ,a ,b (unless (equal ,expression 'fail) t)))))

(defun tablef (a b expression)
  "Prints the truth table of a given logical expression in two variables."
  (loop :for a :in '(t t nil nil)
	:for b :in '(t nil t nil)
	:do (format t "~A~%";;"~{~:[Fail~;True~]~^~T~}~%"
		    ;; A bit awkward but I wanted to use True/Fail instead of
		    ;; Lisp's t/nil but also wanted to use format's iteration
		    ;; and conditional printing feature.
		    (list a b (funcall (lambda (a b) expression) a b)))))
Using logical operators like

Code: Select all

(defun and/2 (a b)
  "A predicate which returns 'true if both arguments are non-nil."
  (cond ((and a b) 'true)
	(t 'fail)))

Code: Select all

(defun swap-operators (expression)
  "A simple function manipulating the order of operator and parameter in simple
  logical expression. (A opr B) --> (opr A B) It is designed to walk down a
  nested listed structure consisting of three-element list."
  (if (and (listp expression) (= (length expression) 3))
      ;;; We are only dealing with valid logical expressions and will not signal any error.
    (let ((first-element (first expression)) (second-element (second expression))
	  (third-element (third expression)))
      (cond ((atom first-element)
	     (list second-element first-element (swap-operators third-element)))
	    (t (list second-element (swap-operators first-element)
		     (swap-operators third-element)))))
    expression))

(defun table2f (a b expression)
  "Prints the truth table of a given logical expression in two variables."
  (table a b (swap-operators expression)))
[1] http://www.informatimago.com/develop/lisp/l99/p46.lisp
[2] http://www.informatimago.com/develop/lisp/l99/p47.lisp

Re: Another question about Macro vs. Function

Posted: Sun Mar 11, 2012 6:40 am
by gugamilare
mabu77 wrote:Hi all,

while fiddling about the 99 Lisp Problems (P46, e.g. [1]) I ran into a situation where I was able to implement a simple solution based on a macro while I am failing getting it done by a function. This gives me a bad feeling and I would love to have someone more experienced comment on my solution. Is it appropriate to use a macro here and why is a function-based solution not working here?
What you are trying to do can't be done by a function, unless you use EVAL or COMPILE. Your function TABLEF is wrong for two reasons:

Code: Select all

(defun tablef (a b expression)
  "Prints the truth table of a given logical expression in two variables."
  (loop :for a :in '(t t nil nil)
	:for b :in '(t nil t nil)
	:do (format t "~A~%";;"~{~:[Fail~;True~]~^~T~}~%"
		    ;; A bit awkward but I wanted to use True/Fail instead of
		    ;; Lisp's t/nil but also wanted to use format's iteration
		    ;; and conditional printing feature.
		    (list a b (funcall (lambda (a b) expression) a b)))))
It seems like the values of A and B in this function are supposed to be symbols. however, LOOP will bind the values of A and B, not of the symbols passed to the function. There is no way of changing the value of a symbol given at run-time unless it's a special symbol (declared with DEFVAR or DEFPARAMETER).

For instance, suppose you call

Code: Select all

(tablef 'x 'y '(and x y))
At the beggining, the value of A is 'X and the value of B is 'Y. When the macro LOOP is executed, the value of both A and B is T. What is the value of X and Y? None! X and Y don't have any values associated with them. And how can you give a value to X and Y in those circunstances? The only way would be to bind the value of the symbols with SYMBOL-VALUE or PROGV, but don't do that, that is not a good idea.

Take a look:

Code: Select all

CL-USER> (defun test (a b)
           (format t "The value of A is ~S.~%" a)
           (format t "The value of B is ~S.~%" b)
           (loop :for a :in '(t t nil nil)
              :for b :in '(t nil t nil)
              :do 
              (format t "Now, the value of A is ~S.~%" a)
              (format t "Now, the value of B is ~S.~%" b)))
TEST
CL-USER> (test 'x 'y)
The value of A is X.
The value of B is Y.
Now, the value of A is T.
Now, the value of B is T.
Now, the value of A is T.
Now, the value of B is NIL.
Now, the value of A is NIL.
Now, the value of B is T.
Now, the value of A is NIL.
Now, the value of B is NIL.
NIL
Now, take a look at the lambda. What does this function return? It returns the value of the variable EXPRESSION - which, in our case, is the list '(and x y). In Lisp, there is no difference between data and code. This function returns the code, it doesn't evaluate the code.

Take a look:

Code: Select all

CL-USER> (defun test2 (a b expression)
           (format t "The value of A is ~S.~%" a)
           (format t "The value of B is ~S.~%" b)
           (format t "The value of EXPRESSION is ~S.~%" expression)
           (loop :for a :in '(t t nil nil)
              :for b :in '(t nil t nil)
              :do 
              (format t "Now, the value of A is ~S.~%" a)
              (format t "Now, the value of B is ~S.~%" b)
              (format t "Now, the value of EXPRESSION is ~S.~%" expression)
              (format t "The value returned by the lambda function is ~S.~%"
                      (funcall (lambda (a b) expression) a b))))
; in: DEFUN TEST2
;     (FUNCALL (LAMBDA (A B) EXPRESSION) A B)
; ==>
;   (SB-C::%FUNCALL (LAMBDA (A B) EXPRESSION) A B)
; 
; caught STYLE-WARNING:
;   The variable A is defined but never used.
; 
; caught STYLE-WARNING:
;   The variable B is defined but never used.
; 
; compilation unit finished
;   caught 2 STYLE-WARNING conditions
TEST2
CL-USER> (test2 'x 'y '(and  x y))
The value of A is X.
The value of B is Y.
The value of EXPRESSION is (AND X Y).
Now, the value of A is T.
Now, the value of B is T.
Now, the value of EXPRESSION is (AND X Y).
The value returned by the lambda function is (AND X Y).
Now, the value of A is T.
Now, the value of B is NIL.
Now, the value of EXPRESSION is (AND X Y).
The value returned by the lambda function is (AND X Y).
Now, the value of A is NIL.
Now, the value of B is T.
Now, the value of EXPRESSION is (AND X Y).
The value returned by the lambda function is (AND X Y).
Now, the value of A is NIL.
Now, the value of B is NIL.
Now, the value of EXPRESSION is (AND X Y).
The value returned by the lambda function is (AND X Y).
NIL
Pay attention to the STYLE-WARNING, telling you that (lambda (a b) expression) doesn't use the value of A or B. Also, see how the value that the lambda function returns is the same value of the variable EXPRESSION.

You basically ran into the same problem in the second example by defining TABLE2F as a function.

Re: Another question about Macro vs. Function

Posted: Sun Mar 11, 2012 8:00 am
by mabu77
Gugamilare,

thanks for your reply and the examples you gave. This was exactly what was driving me to implement table as a macro in the first place. I just got confused when I wasn't able to extend it using the function swap-operators. As I said in a next step i wanted to implement a way to deal with more natural notation for the logical expression (Although I am getting totally used to the prefix notation already), e.g. (A and B) instead of (and A B). I thought for this limited case a simple function which manipulates the expression should be sufficient, e.g. something like this as a first approximation (table is the macro working fine itself):

Code: Select all

CL-USER> (table A B (swap-operators '(A and/2 B)))
True True True
True Fail True
Fail True True
Fail Fail True
But it ends as as can been seen in a problem, leading to "True" in all cases. I think I understand that this is because the macro table is expanded before the function swap-operators is evaluated. The first macro expansion leads to

Code: Select all

(LOOP :FOR A :IN '(T T NIL NIL)
      :FOR B :IN '(T NIL T NIL)
      :DO (FORMAT T "~{~:[Fail~;True~]~^~T~}~%"
                  (LIST A B
                        (UNLESS (EQUAL (SWAP-OPERATORS (A AND/2 B)) 'FAIL) T))))
I guess, again the actual logical expression is not evaluated at run-time. Because swap-operators is supposed to work recursively on nested expressions I hesitated to implement it as a macro. But maybe this is the solution here?

Regards
Martin

Re: Another question about Macro vs. Function

Posted: Sun Mar 11, 2012 2:01 pm
by gugamilare
I think the better way to implement this is to use EVAL like the first version of the function TABLE in P46. And, yes, I think this is a legitimate use of EVAL, because what you want is to evaluate expressions and see the result of the evaluation.

Re: Another question about Macro vs. Function

Posted: Mon Mar 12, 2012 1:01 pm
by mabu77
Okay, I have looked once more at Pascal's solution and do understand now how to implement it as a pure function. Although, I find my macro solution nice because of its brevity. Nevertheless, I would love to understand how one could combine the function swap-operators with the macro table.

Regards
Martin

Re: Another question about Macro vs. Function

Posted: Mon Mar 12, 2012 3:49 pm
by gugamilare
You need to write SWAP-OPERATORS as a macro as well.

The macro solution have one limitation: you can only use expressions that you know at compile-time. If you want to generate an expression at run-time, the macros won't work.