1 / 15

Macros

Macros. “How can you get anything done in [other languages], I think, without macros?” - Paul Graham, 2003. Writing Programs…. … that write programs! Macros work via transformation Macros translate a given LISP expression into another expression Allows for efficient execution.

trista
Download Presentation

Macros

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

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))))

More Related