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