Explanation of macros; Haskell macros
Kenny Tilton
ktilton at nyc.rr.com
Wed Nov 5 23:13:47 EST 2003
Stephen J. Bevan wrote:
> Kenny Tilton <ktilton at nyc.rr.com> writes:
>
>>Stephen J. Bevan wrote:
>> > However, lots
>> > of people take the AWK-like approach because it works for them with
>> > whatever language they are currently using.
>>
>>But the question is "why macros?" Answer, so you do not have to use Awk.
>
>
> That seems to be the question you want to answer, but I don't think it
> is the question others are really asking based on the fact that the CL
> macro examples haven't obviously won many over.
<heh-heh> The stuff I write in response to macro-resistant argumentation
is not for the arguers, it is for lurkers who might actually be thinking
while they are reading. jeez, Paul Graham wrote a whole book on macros,
lispniks swear by macros. That's all it takes to get "I am Curious
(language)" types to get interested.
NG arguers are just NG arguers.
Here's a real-live working macro I used last week:
(defmodel ring-net (family)
(
(ring-ids :cell nil :initform nil
:accessor ring-ids :initarg :ring-ids)
(sys-id :cell nil :initform nil :accessor sys-id :initarg :sys-id)
(reachable-nodes :initarg :reachable-nodes :accessor reachable-nodes
:initform (c? (let (reachables)
(map-routers-up
(lambda (node visited)
(declare (ignore visited))
(push node reachables))
(find (sys-id self) (^kids)
:key 'md-name))
reachables)))
(clock :initform (cv 0) :accessor clock :initarg clock)
))
DEFMODEL wraps DEFCLASS, itself a macro. Let's expand the above:
<<<BEGIN>>>>>
(progn (eval-when (:compile-toplevel :execute :load-toplevel)
(setf (get 'ring-net :cell-defs) nil))
nil nil
(eval-when (:compile-toplevel :execute :load-toplevel)
(setf (md-slot-cell-type 'ring-net 'reachable-nodes) t)
(unless (macro-function '^reachable-nodes)
(defmacro ^reachable-nodes (&optional (model 'self) synfactory)
(excl::bq-list `let (excl::bq-list (excl::bq-list
`*synapse-factory* synfactory))
(excl::bq-list 'reachable-nodes model)))))
(eval-when (:compile-toplevel :execute :load-toplevel)
(setf (md-slot-cell-type 'ring-net 'clock) t)
(unless (macro-function '^clock)
(defmacro ^clock (&optional (model 'self) synfactory)
(excl::bq-list `let (excl::bq-list (excl::bq-list
`*synapse-factory* synfactory))
(excl::bq-list 'clock model)))))
(progn (defclass ring-net (family)
((ring-ids :initform nil :accessor ring-ids
:initarg :ring-ids)
(sys-id :initform nil :accessor sys-id
:initarg :sys-id)
(reachable-nodes :initarg :reachable-nodes
:accessor reachable-nodes
:initform
(c? (let (reachables)
(map-routers-up (lambda
(node
visited)
(declare (ignore visited))
(push
node reachables))
(find
(sys-id self)
(^kids)
:key
'md-name))
reachables)))
(clock :initform (cv 0) :accessor clock
:initarg clock))
(:documentation "chya") (:default-initargs)
(:metaclass standard-class))
(defmethod shared-initialize :after ((self ring-net)
slot-names &rest iargs)
(declare (ignore slot-names iargs))
(unless (typep self 'model-object)
(error "if no superclass of ~a inherits directly
or indirectly from model-object, model-object must be included as a
direct super-class in
the defmodel form for ~a" 'ring-net 'ring-net)))
nil nil
(progn (defmethod reachable-nodes ((self ring-net))
(md-slot-value self 'reachable-nodes))
(defmethod (setf reachable-nodes) (new-value (self
ring-net))
(setf (md-slot-value self 'reachable-nodes)
new-value))
nil)
(progn (defmethod clock ((self ring-net)) (md-slot-value
self 'clock))
(defmethod (setf clock) (new-value (self ring-net))
(setf (md-slot-value self 'clock) new-value))
nil)
(find-class 'ring-net)))
<<<END>>>>
Oh, I'm sorry, did I fill up your hard drive? Were you looking forward
to typing all that by hand? And revisiting a hundred or so like that
when you decided to change what DEFMODEL does?
That is point #1.
Point #2: some but not all of the above went away when I did a version
using a metaclass. But then I wanted to open source the hack, and not
all Lisps do the MOP. The point being that in general one can get very
cool semantics absent cool built-in language stuff precisely because
sufficient plumbing can achieve most cool effects, and macros let one
hide all that plumbing. This point will be too subtle for many, whow
might yell out "But my language has a MOP and there is only one imp". I
am making a more general point: macros mean one is not limited by ones
chosen language.
Point #3: DEFMODEL is part of a VLH (Very Large Hack). Now you might
say, "I don't do Very Large Hacks!". Well, maybe you do, but maybe you
just ahve a lot of function calls all over with a lot of redundant,
boilerplate code arranged Just So to conform to the requirements of the
hack. With macros the boilerplate disappears (and again) you thank your
lucky stars when you make a neat improvement to the VLH and you do not
need to revisit all the boilerplate.
Now let's look in one level, to:
(c? (let (reachables)
(map-routers-up (lambda (node visited)
(declare (ignore visited))
(push node reachables))
(find (sys-id self) (^kids)
:key 'md-name))
reachables))
and expand that:
(MAKE-C-DEPENDENT
:CODE '((LET (REACHABLES)
(MAP-ROUTERS-UP (LAMBDA (NODE VISITED)
(DECLARE (IGNORE VISITED))
(PUSH NODE REACHABLES))
(FIND (SYS-ID SELF) (^KIDS) :KEY 'MD-NAME))
REACHABLES))
:RULE (C-LAMBDA (LET (REACHABLES)
(MAP-ROUTERS-UP (LAMBDA (NODE VISITED)
(DECLARE (IGNORE VISITED))
(PUSH NODE REACHABLES))
(FIND (SYS-ID SELF) (^KIDS)
:KEY 'MD-NAME))
REACHABLES)))
That exaggerates things a little, because that code keyword is receiving
at runtime the /source/ of the form expanded into the code to be
compiled as an anonymous function, so I can figure out what code crashed
when I end up in a backtrace. Sorry if I scared the children with that one.
Now don't feel bad if you don't recognize C-LAMBDA, that's mine:
(LAMBDA (#:G1000 &AUX (SELF (C-MODEL #:G1000))
(.CACHE (C-VALUE #:G1000)))
(DECLARE (IGNORABLE .CACHE SELF))
(ASSERT (NOT (CMDEAD #:G1000)) NIL
"cell dead entering rule ~a" #:G1000)
(LET (REACHABLES)
(MAP-ROUTERS-UP (LAMBDA (NODE VISITED)
(DECLARE (IGNORE VISITED))
(PUSH NODE REACHABLES))
(FIND (SYS-ID SELF) (^KIDS)
:KEY 'MD-NAME))
REACHABLES))
Still there? OK, this VLH has many different kinds of macros such as C?,
but they all get called in exactly one place. They need the same lambda
signature. Before I had C-LAMBDA, a rare change to that signature was a
real pain. No mas.
OK, by now I am sure you are fascinated by: (^kids)
Expanded, we get: (KIDS SELF)
Beezlebub! Don't worry, it compiles. The ^thingy macros are meant to be
invoked only in lexical contexts supplying the Smalltalk-like ubiquitous
anaphor SELF. look back at the expansion of c-lambda to see SELF being
pulled out of the model slot of the cell. ie, big bad variable capture
has been harnessed for a good cause, emulating the anaphoric variables
of SELF and THIS of other languages. I scared a grown-up Lispnik with
this, once.
^these used to do even more work, but an improvement made them (almost)
unnecessary, but I kept them because I need them for the following and
because I kinda dig the way they shout out "I am using one of my own
slots!".
POINT #4: Sometimes macros just brighten up the code a little.
Where they are still needed is:
code: (^clock system (fSensitivity 30))
expansion: (LET ((*SYNAPSE-FACTORY* (FSENSITIVITY 30)))
(CLOCK SYSTEM))
whoa! where did all that come from? the macro decided to write a little
more code when it saw me using the synapse option. Now the important
thing here is that *SYNAPSE_FACTORY* gets picked up in the access done
by (clock system), so ya can't do a high-level function:
(func-to-apply-synapse (FSensitivity 30) (clock system))
and you can't pass it to the clock function because that is an accessor
and good CLOSians don't fuck with accessor signatures.
in fact whatever trick you come up with would be uglier than:
(LET ((*SYNAPSE-FACTORY* (FSENSITIVITY 30)))
(CLOCK SYSTEM))
So yer stuck with it. Not me, tho.
POINT #5: Sometimes macros brighten up the code /and/ hide necessary
plumbing.
Now getting back to the VLH issue, all the above is about taking
something developers do in any language from 6502 to Lisp, viz, create
little code worlds in which functions and variables and stuff are
regularly used repeatedly many times, and allowing one's implementation
to seemingly expand to include the new custom code world, hiding a lot
of boilerplate in the process.
Now I know what you are going to say. No one will buy any of that
because they have no fucking idea what I am talking about. Ah, but they
can tell I am having a /lot/ of fun, and <cue Burdick> if you check the
highlight film below, you'll find a whole section on Hedonists.
kenny
--
http://tilton-technology.com
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
More information about the Python-list
mailing list