Presentation is loading. Please wait.

Presentation is loading. Please wait.

Macros “How can you get anything done in [other languages], I think, without macros?” - Paul Graham, 2003.

Similar presentations


Presentation on theme: "Macros “How can you get anything done in [other languages], I think, without macros?” - Paul Graham, 2003."— Presentation transcript:

1 Macros “How can you get anything done in [other languages], I think, without macros?” - Paul Graham, 2003

2 Writing Programs… … that write programs! Macros work via transformation – Macros translate a given LISP expression into another expression Allows for efficient execution

3 Two Similar Functions (defun print-file (file) (with-open-file (str file) (do ((line (read-line str nil nil) (read-line str nil nil))) ((null line) nil) (format t "~A~%" line) ))) (defun find-first-comment (file) (with-open-file (str file) (do ((line (read-line str nil nil) (read-line str nil nil))) ((null line) nil) (when (eql (char line 0) #\;) (return line)) )))

4 Desire to Generalize If this pattern reoccurs often, we would like to do something simpler like (defun print-file (file) (dofile line file (format t "~A~%" line) )) (defun find-first-comment (file) (dofile line file (when (eql (char line 0) #\;) (return line)) )) But how would you do this with a function? line is being passed as a variable name…

5 Solution is Macros What a function does – 1. Evaluate each argument – 2. Evaluate the body of the function What a macro does – 1. Evaluate body of macro – 2. Evaluate resulting body of expanded macro

6 Defining Macros Similar to defun (defmacro macro-name (argument*) S-expr*) (defmacro dofile (var file &rest body) (list 'with-open-file (list 'str file) (list* 'do (list (list var (list 'read-line 'str 'nil 'nil) (list 'read-line 'str 'nil 'nil))) (list (list 'null var) 'nil) body))) But this is impossibly ugly

7 Backquote The backquote ( ` ), like single quote ( ' ), can be used to inhibit evaluation >`(a b c d) (A B C D) >`(setf x (* 3 7)) (SETF X (* 3 7)) Unlike single quote, can specify partial evaluation by using a comma (, ) >`(setf x,(* 3 7)) (SETF X 21)

8 Simplified Macro with Backquote We can make our macro definition more readable with the backquote (defmacro dofile (var file &rest body) `(with-open-file (str,file),(list* 'do `((,var (read-line str nil nil) (read-line str nil nil))) `((null,var) nil) body))) Still kind of ugly. Problem is that body can be a list of commands, so we need list*

9 Splice The splicing operator (,@ ) allows the outer parentheses of an expression to be removed >`(+,@(cdr '(2 4 6))) (+ 4 6) Now we can fix our macro (defmacro dofile (var file &rest body) `(with-open-file (str,file) (do ((,var (read-line str nil nil) (read-line str nil nil))) ((null,var) nil),@body) ))

10 Macroexpand A tool for debugging macros, it expands a macro and suppresses second evaluation, returning the expanded expression >(defmacro front (some-list) (list 'car some-list)) FRONT >(macroexpand '(front '(1 2 3))) (CAR '(1 2 3)) T If your macro definition contains macros in it, then they will also be expanded, which may result in code you don’t recognize

11 Specializing the Input Currently we call our macro using (dofile var file body*) But we can group our arguments like so (dofile (var file) body*) If we parenthesize them in the macro definition Also, in macros, &body is preferred in place of &rest, for formatting reasons. So now we have: (defmacro dofile ((var file) &body body) `(with-open-file (str,file) (do ((,var (read-line str nil nil) (read-line str nil nil))) ((null,var) nil),@body) ))

12 Problems (1) This code works: (defun copy (source out) (with-open-file (outstr out :direction :output) (dofile (line source) (format outstr "~A~%" line)))) But this almost identical code doesn’t (defun copy (source out) (with-open-file (str out :direction :output) (dofile (line source) (format str "~A~%" line))))

13 Problems (2) If we expand the macro definition, we see why (defun copy (source out) (with-open-file (str out :direction :output) (with-open-file (str source) (do ((line (read-line str nil nil) (read-line str nil nil))) ((null line) nil) (format str "~A~%" line))) )) There are name collisions between the variables in the macro and those in the function definition. The only way to avoid this is by knowing the internals of the macro, which is unacceptable. We need to fix the macro again

14 Gensym gensym generates a new symbol name that is guaranteed to be unique We use it to get unique variable names for our macro (changes in red) (defmacro dofile ((var file) &body body) (let ((str (gensym))) `(with-open-file (,str,file) (do ((,var (read-line,str nil nil) (read-line,str nil nil))) ((null,var) nil),@body) )))

15 Macroexpand After Gensym >(macroexpand '(dofile (line "file.txt") nil)) (LET ((#:G1478 (OPEN "file.txt"))) (UNWIND-PROTECT (PROGN (BLOCK () (LET ((LINE (READ-LINE #:G1478 NIL NIL))) (TAGBODY #:G1479 (IF (NULL LINE) (RETURN (PROGN NIL))) (TAGBODY NIL) (PSETQ LINE (READ-LINE #:G1478 NIL NIL)) (GO #:G1479))))) (IF #:G1478 (CLOSE #:G1478))))


Download ppt "Macros “How can you get anything done in [other languages], I think, without macros?” - Paul Graham, 2003."

Similar presentations


Ads by Google