10 macros. lisp code is expressed as lists, which are lisp objects this makes it possible to write...

Post on 16-Dec-2015

250 Views

Category:

Documents

4 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Functional Programming

10 Macros

Introduction

Lisp code is expressed as lists, which are Lisp objects This makes it possible to write programs that

would write programs This lecture shows how to cross the line from

expressions to code

Eval

How to generate expressions? Just call list

How to treat expressions as code? Use the function EVAL > (eval ’(+ 1 2 3))

6 > (eval ’(format t “Hello”))

HelloNIL

Eval

(defun our-toplevel ( ) (do ( ) (nil) (format t “~%> “) (print (eval (read)))))

The toplevel is also known as a read-eval-print loop → 把所讀到的東西給 evaluate 出來

Eval

Calling eval is one way to cross the line between lists and code. However, it’s not a very good way: It’s inefficient The expression is evaluated with no lexical context▪ > (let ((x 1)) (eval ’(+ x 3)))

*** - EVAL: variable X has no value

Macros

The most common way to write programs that write programs is by defining macros

(defmacro nil! (x) (list ’setf x nil)) ; 用 list 來組一個 list

A call of the form (nil! a) will be translated into (setf a nil)

> (nil! x)NIL

> xNIL

Macros

To test a function, we call it, but to test a macro, we look at its expansion > (macroexpand-1 ’(nil! x))

(SETF X NIL)T

A macro call can expand into another macro call. When the compiler (or toplevel) encounters a macro call, it simply keeps expanding it until it is no longer one ( 展開到沒有為止 )

Macros

When you use defmacro, you’re defining a function much like: (lambda (expr)

(apply #’(lambda (x) (list ’setf x nil)) (cdr expr)))

若 input 是 (nil! a) 則會 return (setf a nil)

Backquote

Like a regular quote, a backquote alone protects its argument from evaluation > ‘(a b c)

(A B C) The advantage of backquote is: within a backquote

expression, you can use , (comma) and ,@ (comma-at) to turn evaluation back > (setf a 1 b 2)

2> ‘(a is ,a and b is ,b)(A IS 1 AND B IS 2)

Backquote

By using backquote instead of a call to list, we can write macro definitions that look like the expressions they will produce (defmacro nil! (x)

‘(setf ,x nil))

> (setf lst ’(a b c))(A B C)> ‘(lst is ,lst)(LST IS (A B C))> ‘(its elememts are ,@lst)(ITS ELEMENTS ARE A B C)

Backquote > (setf x ’(1 2))

(1 2)> ‘(x ,x ’,x ,@x)(X (1 2) ’(1 2) 1 2)

(defmacro test (x) (setf x nil)) (test a) → 會被展開為 NIL > (macroexpand ’(test a))

NIL;T

(defmacro test2 (x) ’(setf x nil)) > (macroexpand ’(test2 a))

(SETF X NIL);T

> (test2 a)NIL

> a *** - EVAL: variable A has no value

> xNIL

Backquote

Comma-at is useful in macros that have rest parameters representing, for example, a body of code

Suppose we want a while macro that will evaluate its body so long as an initial test expression remains true > (let ((x 0))

(while (< x 10) (princ x) (incf x)))0 1 2 3 4 5 6 7 8 9NIL

We can define such a macro by using a rest parameter to collect a list of the expressions in the body, then using comma-at to splice this list into the expansion:(defmacro while (test &rest body) ‘(do ( ) ((not ,test)) ,@body))

Macro design

想寫一個程式 , 可以 evaluate 他的 body n 次例如 :> (ntimes 10 (princ “.”))……….NIL

如果這樣寫

(defmacro ntimes (n &rest body) ‘(do ((x 0 (+ x 1))) ((>= x ,n)) ,@body))

看起來正確 , 但是某種情況下會有問題

Macro design

若 macro 裡頭的變數名稱跟原來程式中的衝到 , 例如 :> (let ((x 10)) (ntimes 5 (setf x (+ x 1))) x)10

(let ((x 10)) (do ((x 0 (+ x 1))) ((>= x 5)) (setf x (+ x 1))) x)

Macro design

解決方式 : 用 gensym(defmacro ntimes (n &rest body) (let ((g (gensym))) ‘(do ((,g 0 (+ ,g 1))) ((>= ,g ,n)) ,@body)))

但是 , 另一個問題 : 如果第一個參數 n 用一個 expression 取代 ,那麼每次做到有 n 地方 , 都會 evaluate 一次 , 例如 : 若第一個參數為 (setf v (- v 1)), 亦即 ,> (let ((v 10)) (ntimes (setf v (- v 1)) (princ “.”)))…..NIL 我們本來希望有 9 個點 , 現在只剩 5 個

Macro design

因為前述的 macro 會被展成 :(let ((v 10)) (do ((#:g1 0 (+ #:g1 1))) ((>= #:g1 (setf v (- v 1)))) (princ “.”)))迴圈每次做到 ((>= #:g1 (setf v (- v 1))))v 的值都會變化 , 因此 #:g1 不再是跟 9 比較

因此 , 我們修正為 :(defmacro ntimes (n &rest body) (let ((g (gensym)) (h (gensym))) ‘(let ((,h ,n)) ; 先把 n 算好給 h, 使得迴圈中不會動到 (do ((,g 0 (+ ,g 1))) ((>= ,g ,h)) ,@body))))

Macro design

看看 Common Lisp 裏頭內建的 macro > (pprint (macroexpand-1 ’(cond (a b)

(c d e) (t f))))(IF A B (IF C (PROGN D E) F))

pprint 可以以縮排形式將 code 印出

Macro design

(defmacro cah (lst) ‘(car ,lst)) ; 如果 macro 的定義中有可以被 setf 的function

> (let ((x (list ’a ’b ’c))) (setf (cah x) 44) ; 則 macro 也可以被setf x)(44 B C)

Macro design 寫一個東西來計算平均

> (avg 2 4 8)14/3

寫成 function▪ (defun avg (&rest args)

(/ (apply #’+ args) (length args))) 寫成 macro▪ (defmacro avg (&rest args)

‘(/ (+ ,@args) ,(length args)))

若用 function,args 的長度是在 run-time 時計算 , 而若用 macro, 則在 compile-time 即計算完成

Macro design

Homework▪ Imagine that the designers of LISP had forgotten to

include the UNLESS primitive. Now you decide to define your own UNLESS primitive, which you call WHEN-NIL. Define WHEN-NIL such that it translates(when-nil <trigger> <result>)into(when (not <trigger>) <result>)

Macro design

(defmacro when-nil (trigger result) ‘(when (not ,trigger) ,result))

(defmacro when-nil (trigger &rest result) ‘(when (not ,trigger) ,@result))

top related