Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   Python (http://www.velocityreviews.com/forums/f43-python.html)
-   -   Explanation of macros; Haskell macros (http://www.velocityreviews.com/forums/t323294-explanation-of-macros-haskell-macros.html)

mike420@ziplip.com 10-06-2003 11:34 PM

Explanation of macros; Haskell macros
 
I think others dropped the ball while trying to explain macros
to non-Lispers recently. The examples I saw posted were either
too difficult or easily doable without macros (maybe even easier).
This probably convinced many Pythonistas that Lispers are
complexity-seeking wackos, and macros are cruft. This only
applies to some people and *their* macros. Most important,
all of the examples failed to relate to "the common man" :
typical python user or programming newbie. As they say,
"if you can't explain it ..."

I intend to correct this. The following example relates to
a problem familiar to all Python users and demonstrates
two uses of macros: syntax improvement and efficiency gain
through compile-time code introspection.

Let's say you do not have the "for" keyword, but you have
"dolist" for iterating a list and "dotimes" - a simple
index loop. You want to create "for" just like in Python, and
you also want "for i in range(10000): print i" to be efficient
and avoid constructing the big list (maybe you do not have
enough memory). In Lisp, you can acomplish this with the
following macro:

(defmacro for(i in list &rest rest)
(if (eql 'in in)
(if (and (listp list)
(eql (length list) 2)
(eql 'range (first list))
(integerp (second list)))
`(dotimes (,i ,(second list)) ,@rest)
`(dolist (,i ,list) ,@rest))
(error "syntax error")))


You can use it like this:

(for k in (range 10000)
(print k))

or

(for k in '(666 222 333 111)
(print "foo")
(print k))

The macro works by looking at *code* (not values) at
*compile-time*, and if the right argument is (range xxxx),
it uses the more efficient "dotimes".

Let me know if the above helped you understand the usefulness
of macros or was just as sucky as the earlier stuff.

BTW, I think Alex made some very good points about
simplicity and uniformity. I hesitate to say that I agree
with the final verdict, but his reasoning is very sound.
Lispers who tried to mock him (some outside CLP) just showed
their own idiocy. This isn't winning Lisp any friends...

Someone pointed out that Haskell has macros. Does anyone
know how they relate to Lisp and Scheme macros? Better, worse,
different? If anyone knows them well, can you show how you
would do "for i in ...." using Haskell macros?

Tomasz Zielonka 10-07-2003 01:16 AM

Re: Explanation of macros; Haskell macros
 
mike420@ziplip.com wrote:
> Someone pointed out that Haskell has macros. Does anyone
> know how they relate to Lisp and Scheme macros? Better, worse,
> different?


You can find a short comparison of Scheme and Template Haskell (TH) approach
in (pages 12-13):

http://www.haskell.org/th/papers/meta-haskell.ps

I would say that similarity between them is much bigger than between,
say, TH and C++ template meta-programming, or between Scheme and C++ tmp.

> If anyone knows them well, can you show how you would do "for i in
> ...." using Haskell macros?


Haskell macros/templates provide mechanisms for introspection. They
allow you to process Haskell abstract syntax trees as values of
algebraic datatype Exp. That's probably a little more complicated than
in LISP and Scheme, because Haskell's syntax is much more complex.

You could implement "for i in ..." using them, but this would be a bad
example, because as a non-strict (think "lazy") language it will handle
this code gracefully by default.

For example this code prints consecutive positive Integers starting from
1 (if run in the IO monad):

mapM_ print [1..]

If mapM_ scares you, you can define

for l f = mapM_ f l

and write it nicely as:

for [1..] print

Besides, Haskell macros aren't applied everywhere by default as in LISP.
You have to ,,splice'' them explicitely (see the paper).

A good example of TH's power is the ability to create a statically
type-checked printf mechanism using format strings like in C. Example:

$(printf "an int: %d, int in hex: %08x, a string: %20s") 10 255 "foo"

compiles and evaluates to

"an int: 10, int in hex: 000000ff, a string: foo"

but

$(printf "an int: %d, int in hex: %08x, a string: %20s") 10 "foo" 255

will raise a compile-time type error.




Recently I have used TH to generate a datatype enumeration all keywords
in some SQL dialect and to create a mapping from strings to these
datatypes. It looks like this:

keywords :: [String]
keywords = words
" ADD ALL ALTER AND ANY AS ASC AUTHORIZATION BACKUP BEGIN BETWEEN \
[... 160 keywords snipped ...]
\ VALUES VARYING VIEW WAITFOR WHEN WHERE WHILE WITH WRITETEXT "

kwConName :: String -> String
kwConName = id

-- generates abstract syntax for declaration of Keyword datatype
dataKeyword :: Dec
dataKeyword =
Data
[] -- context
"Keyword" -- datatype name
[] -- type variables
(map (\k -> Constr (kwConName k) []) keywords) -- constructors
(words "Show Eq Ord Enum Bounded") -- derived instances

-- generates abstract syntax for a list of pairs like ("SELECT", SELECT)
-- for all keywords
keywordMapList :: ExpQ
keywordMapList =
foldr
(\k l ->
let str = return (Lit (String k))
con = return (Con (kwConName k))
in [| ($str, $con) : $l |])
[| [] |]
keywords

Then in another module:

$(return [dataKeyword])

keywordMap :: FiniteMap String Keyword
keywordMap = listToFM $(keywordMapList)

This way I am sure that every keyword is included in the mapping.

Best regards,
Tom

--
..signature: Too many levels of symbolic links

prunesquallor@comcast.net 10-07-2003 02:51 AM

Re: Explanation of macros; Haskell macros
 
mike420@ziplip.com writes:

> I think others dropped the ball while trying to explain macros
> to non-Lispers recently. The examples I saw posted were either
> too difficult or easily doable without macros (maybe even easier).


Macros from production code:

(defmacro debug-message (noise format-string &rest args)
"Print a message on *DEBUG-IO* using FORMAT-STRING and ARGS
iff the *DEBUG-NOISE-LEVEL* is equal to or greater than NOISE.

When writing code, sprinkle calls to DEBUG-MESSAGE at strategic
points to aid in debugging. The different noise levels should be
used at different semantic levels in the code. Level 0 is
for only the highest level of functionality, Level 3 is for
module level, Level 5 is for extreme detail."
(if (and (boundp '*disable-debug-messages*)
(eq *disable-debug-messages* t))
`(PROGN)
(let ((noise-var (gensym "NOISE-VAR-")))
`(LET ((,noise-var ,noise))
#+ALLEGRO (DECLARE (:FBOUND FORMAT-DEBUG-MESSAGE))
(WHEN-DEBUGGING ,noise-var
(FORMAT-DEBUG-MESSAGE ,noise-var ,format-string (LIST ,@args)))))))

This macro lets me write thing like:

(debug-message 2 "Beginning major phase ~s" *phase*)

at various places in the code. The amount of debugging noise is
determined by a global variable. If the variable
*DISABLE-DEBUG-MESSAGES* is bound to 't at compile time, the code is
omitted completely. This is done when a customer build is created.

(defmacro ignore-errors-unless (condition &body forms)
"Unless CONDITION evaluates to TRUE, act as IGNORE-ERRORS does while executing FORMS, with the
the same return values. If CONDITION evaluates to TRUE, when we don't ignore errors, however
the return value is as if an IGNORE-ERRORS were around the form.
Example: (ignore-errors-unless *debugging* (do stuff) (do more stuff) ...)
(ignore-errors-unless (eq *debugging* 2) (do stuff) (do more stuff) ...)"
(let ((block-name (gensym "IGNORE-ERRORS-UNLESS-BLOCK-"))
(cond-value (gensym "IGNORE-ERRORS-UNLESS-COND-VALUE-")))
;; Evaluate the cond value before the body for less confusing semantics.
;; (We don't want the block established around the conditional).
`(LET ((,cond-value ,condition))
(BLOCK ,block-name
(HANDLER-BIND (#+allegro (EXCL:INTERRUPT-SIGNAL #'SIGNAL)

(CL:ERROR (FUNCTION
(LAMBDA (CONDITION)
#+ALLEGRO (DECLARE (:FBOUND DEBUG-NOTE-CONDITION))
(UNLESS ,cond-value
(DEBUG-NOTE-CONDITION "caught by an ignore-errors-unless form" CONDITION)
(RETURN-FROM ,block-name
(values NIL CONDITION)))))))
(VALUES (LOCALLY ,@forms) NIL))))))

As you might imagine, this one discards errors unless some
condition is true.

(defmacro ignore-errors-unless-debugging (&rest body)
"Just like IGNORE-ERRORS, except that if *debug-noise-level* is non-nil,
errors are not ignored."
`(IGNORE-ERRORS-UNLESS (AND *DEBUG-NOISE-LEVEL*
(NULL *IGNORE-ERRORS-EVEN-IF-DEBUGGING*))
,@body))

Very handy when debugging a server. When serving web requests, you don't
want errors to take down the entire server. Yet when you are debugging
it, you don't want to suppress them. Of course, when you are running
test regressions during debugging, you *do* want to suppress them....


Occasionally I have to move large amounts of data very fast. There
are common-lisp functions for doing this, but sometimes you need to go
flat out as fast as possible. This generally requires adding a lot of
declarations. But a function that is declared to operate solely on,
say, 1-byte wide vectors looks amazingly like one that operates on
2-byte wide vectors, except for the declarations. Hence the macro:

(define-fast-subvector-mover %simple-subvector-8b-move simple-array (unsigned-byte 8))

which expands into:

(PROGN
(DECLAIM (FTYPE
#'((SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)) (INTEGER 0 (8388608)) (INTEGER 0 (8388608)) (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)) (INTEGER 0 (8388608)))
%SIMPLE-SUBVECTOR-8B-MOVE-LEFT
%SIMPLE-SUBVECTOR-8B-MOVE-RIGHT))
(DEFUN %SIMPLE-SUBVECTOR-8B-MOVE-LEFT (SOURCE SRC-START SRC-LIMIT DEST DEST-START)
(DECLARE (TYPE (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)) SOURCE)
(TYPE (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)) DEST)
(TYPE (INTEGER 0 (8388608)) SRC-START SRC-LIMIT DEST-START)
(OPTIMIZE
(COMPILATION-SPEED 0)
(DEBUG 0)
(SAFETY 0)
(SPACE 0)
(SPEED 3)))
(PROGN
(LOOP
(PROGN
(WHEN (= SRC-START SRC-LIMIT) (RETURN-FROM NIL NIL))
(SETF (AREF DEST DEST-START) (AREF SOURCE SRC-START))
(INCF SRC-START)
(INCF DEST-START)))))
(DEFUN %SIMPLE-SUBVECTOR-8B-MOVE-RIGHT (SOURCE SRC-START SRC-LIMIT DEST DEST-START)
(DECLARE (TYPE (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)) SOURCE)
(TYPE (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)) DEST)
(TYPE (INTEGER 0 (8388608)) SRC-START SRC-LIMIT DEST-START)
(OPTIMIZE
(COMPILATION-SPEED 0)
(DEBUG 0)
(SAFETY 0)
(SPACE 0)
(SPEED 3)))
(PROGN
(PROGN
(INCF DEST-START (- SRC-LIMIT SRC-START))
(LOOP
(WHEN (= SRC-LIMIT SRC-START) (RETURN-FROM NIL NIL))
(DECF SRC-LIMIT)
(DECF DEST-START)
(SETF (AREF DEST DEST-START) (AREF SOURCE SRC-LIMIT)))))))

A decent lisp compiler can compile this into *very* tight code.
But even better is the fact that if you need to move 2-byte arrays
around, you don't have to write anything more than:

(define-fast-subvector-mover %simple-subvector-16b-move simple-array (unsigned-byte 16))


Let us suppose that we have a function that takes function
as an argument. For instance,

(defun my-mapc (func list)
(dolist (element list)
(funcall func element)))

Now this works, but in performance critical code it may
be a bottleneck (closure creation and funcalling).
It'd be nice if the compiler knew how to handle this specially.

This following function is a parser for literal lambda expressions:

(defun destructure-function-lambda (arity fl receiver if-not-function)
"If fl is of the form (FUNCTION (LAMBDA (bound-variable-list) docstring decls body))
invoke receiver on the bound-variable-list, docstring, decls, and the body.

If fl is of the form (FUNCTION name), invoke receiver on a
fake eta-expanded form.

If fl is of the form NAME, invoke receiver on a
fake eta-expanded form.

Otherwise invoke if-not-function."
(macrolet ((list-length-equals-one (list)
`(AND (CONSP ,list)
(NULL (CDR ,list))))

(list-length-greater-than-one (list)
`(AND (CONSP ,list)
(CONSP (CDR ,list))))

(is-function-form (form)
`(AND (CONSP ,form)
(EQ (CAR ,form) 'FUNCTION)
(LIST-LENGTH-EQUALS-ONE (CDR ,form))))

(function-form-body (function-form)
`(CADR ,function-form))

(is-lambda-form (form)
`(AND (CONSP ,form)
(EQ (CAR ,form) 'LAMBDA)
(LIST-LENGTH-GREATER-THAN-ONE (CDR ,form))))

(lambda-form-arguments (lambda-form)
`(CADR ,lambda-form))

(lambda-form-body (lambda-form)
`(CDDR ,lambda-form)))

(cond ((is-function-form fl)
(let ((pl (function-form-body fl)))
;; Look for `(LAMBDA ...)
(cond ((is-lambda-form pl)
(multiple-value-bind (docstring declarations body)
(split-declarations (lambda-form-body pl))
(funcall receiver (lambda-form-arguments pl) docstring declarations body)))

;; can't fake eta expand if arity is unknown
((null arity) (funcall if-not-function))

((symbolp pl) ; is something like (function foo)
;; perform eta expansion
(let ((arglist nil))
(dotimes (i arity)
(push (gensym "ARG-") arglist))
(funcall receiver arglist nil nil `((,pl ,@arglist)))))

(t (funcall if-not-function)))))

;; Look for naked '(lambda ...)
;; treat as if it were '(function (lambda ...))
((is-lambda-form fl)
(multiple-value-bind (docstring declarations body)
(split-declarations (lambda-form-body fl))
(funcall receiver (lambda-form-arguments fl) docstring declarations body)))

;; Can't fake an eta expansion if we don't know the arity.
((null arity) (funcall if-not-function))

;; Perform an ETA expansion
((symbolp fl)
(let ((arglist nil))
(dotimes (i arity)
(push (gensym "ARG-") arglist))
(funcall receiver arglist nil nil `((FUNCALL ,fl ,@arglist)))))

(t (funcall if-not-function)))))

Now we can use it as follows:

(defmacro my-mapc (func list)
(destructure-function-lambda 1 func
(lambda (bvl docstr decls body)
(declare (ignore docstr))
`(DOLIST (,(car bvl) ,list)
,@decls
,@body))
(lambda ()
(error "~s cannot be destructured." func))))


And here is the result:

(macroexpand-1 '(my-mapc #'(lambda (x) (+ x 2)) some-list))
=> (DOLIST (X SOME-LIST) (+ X 2))

(macroexpand-1 '(my-mapc (lambda (x) (+ x 2)) some-list))
=> (DOLIST (X SOME-LIST) (+ X 2))

(macroexpand-1 '(my-mapc #'print some-list))
=> (DOLIST (#:ARG-9909 SOME-LIST) (PRINT #:ARG-9909))



When I want to emit a web page with an applet on it,
there is a ton of boilerplate that has to go out. But the
boilerplate is parameterized, so you can't just write a text
file and be done with it. This macro abstracts that away:

(defmacro html-with-applet (req &body body)
(with-unique-names (command ticket comment success-page fail-page)
`(PROGN
(EMIT-HTML-HEADER (NET.ASERVE:REQUEST-REPLY-STREAM ,req))
(MACROLET ((EMBED-APPLET (,COMMAND ,TICKET ,COMMENT ,SUCCESS-PAGE ,FAIL-PAGE)
`(NET.ASERVE::HTML
((:div :id "applet-container")
(:P ,,comment)
((:object
:code "Applet.class"
:id "applet"
:width "100%"
:height "300"
:standby "Loading applet...")
((:param :name "URL"
:value (render-uri (extend-uri-query
(net.aserve:request-uri req)
'(:absolute "applet-callback.htm")
`((:command . ,',,command)
(:ticket . ,,,ticket)))
nil)))
((:param name "SUCCESSURL" value (render-uri
(extend-uri-query
(net.aserve:request-uri req)
`(:absolute ,',,success-page)
`((:ticket . ,,,ticket)))
nil)))
((:param name "FAILURL" value (render-uri
(extend-uri-query
(net.aserve:request-uri req)
`(:absolute ,',,fail-page)
`((:ticket . ,,,ticket)))
nil))))))))
(NET.ASERVE::HTML ,@body))
(EMIT-HTML-TRAILER (NET.ASERVE:REQUEST-REPLY-STREAM ,req)))))

This macro writes a macro with backquoted list structure in it.
The user of this macro simply writes:

(embed-applet :file-browser
ticket
"Select a file."
"file-selected.htm"
"cancel.htm")

Within the template.

I don't think these are too complicated to understand, and
although some of them could be handled by functions and such,
they pretty much capture *exactly* what I want write. You don't
need to understand `idioms' and `patterns' to know to
embed an applet.

Terry Reedy 10-07-2003 03:08 AM

Re: Explanation of macros; Haskell macros
 

<mike420@ziplip.com> wrote in message
news:GGDUFQOGIAD5H2L3GDEKNREOMFKFLREPEFD3MGCP@zipl ip.com...
> enough memory). In Lisp, you can acomplish this with the
> following macro:


I presume this is specifically Common Lisp

[ for i in seq example ]

> Let me know if the above helped you understand the usefulness
> of macros or was just as sucky as the earlier stuff.


Yes, I understood even though I am not familiar with the ` , & and @
prefixes.

Terry J. Reedy



Kenny Tilton 10-07-2003 04:13 AM

Re: Explanation of macros; Haskell macros
 


mike420@ziplip.com wrote:
> I think others dropped the ball while trying to explain macros
> to non-Lispers recently.


....<snip>

> Let's say you do not have the "for" keyword, but you have
> "dolist" for iterating a list and "dotimes" - a simple
> index loop. You want to create "for" just like in Python,


Wow, talk about dropping the ball. Translation: "You should learn Lisp
because it has macros so you can recreate Python in Lisp and not learn
Lisp."

If you want to be an educator, you gotta motivate your students. Your
macro was fine in all regards except that its motivation ("avoid Lisp by
learning advanced Lisp!") is, um, unconvincing.

kenny



Rolf Wester 10-07-2003 07:33 AM

Re: Explanation of macros; Haskell macros
 
mike420@ziplip.com wrote:
> Let's say you do not have the "for" keyword, but you have
> "dolist" for iterating a list and "dotimes" - a simple
> index loop. You want to create "for" just like in Python, and
> you also want "for i in range(10000): print i" to be efficient
> and avoid constructing the big list (maybe you do not have
> enough memory). In Lisp, you can acomplish this with the
> following macro:
>
> (defmacro for(i in list &rest rest)
> (if (eql 'in in)
> (if (and (listp list)
> (eql (length list) 2)
> (eql 'range (first list))
> (integerp (second list)))
> `(dotimes (,i ,(second list)) ,@rest)
> `(dolist (,i ,list) ,@rest))
> (error "syntax error")))
>
>

What will a Pythonista think about Lisp macros when he/she tries:

(defun f (n)
(for i in (range n)
(print i)))
(f 10000)

Maybe the macro should better be written:

(defmacro for (i in list &rest rest)
(if (eql 'in in)
(if (and (listp list)
(eql (length list) 2)
(eql 'range (first list))
(or (integerp (second list)) (symbolp (second list))))
`(if (integerp ,(second list))
(dotimes (,i ,(second list)) ,@rest)
(error "not an integer"))
`(dolist (,i ,list) ,@rest))
(error "syntax error")))


Rolf Wester


Richie Hindle 10-07-2003 11:45 AM

Re: Explanation of macros; Haskell macros
 

[Mike]
> The following example relates to
> a problem familiar to all Python users and demonstrates
> two uses of macros: syntax improvement and efficiency gain
> through compile-time code introspection.


[Clear lisp example snipped]

> Let me know if the above helped you understand the usefulness
> of macros or was just as sucky as the earlier stuff.


It certainly helped me to understand, yes. Thanks for posting it.

--
Richie Hindle
richie@entrian.com



Evan Simpson 10-07-2003 04:24 PM

Re: Explanation of macros; Haskell macros
 
mike420@ziplip.com wrote:
> I think others dropped the ball while trying to explain macros
> to non-Lispers recently.


I may be more familiar than most Pythonistas with the potential and
difficulties of macros in Python, from my work in building Zope's Script
objects.

Some background: Python-based Zope Scripts (there are Perl-based ones as
well) provide the ability to execute Python code in an environment in
which every object access has to be vetted by a security system. Early
versions used Michael Hudson's excellent and dangerous bytecodehacks
package, and we later switched over to the compiler package. In both
cases, we were taking parsed Python code and changing its semantics --
slightly in the case of object access, and more blatantly in hijacking
the print statement.

As I learned to use the compiler package, it became obvious that the
same technique could be used to create hygenic macros. For example, it
wouldn't be difficult to take the following Python snippet:

MACRO(Printall, i)

....which parses into the AST (slightly abbreviated):

Discard(CallFunc(Name('MACRO'),
[Name('Printall'), Name('i')],
None, None) )

....and recognize the pattern Discard(CallFunc(Name('MACRO'), *). It can
then be transformed, perhaps into:

For(AssName('i*', 'OP_ASSIGN'),
CallFunc(Name('range'), [Name('i')], None, None),
Stmt([Printnl([Name('i*')], None)]), None)

....thus making the original snippet behave the same as:

for i_ in range(i):
print i

....but with none of the potential indentation and variable-clash issues
that a source code search-and-replace approach would encounter.

Unfortunately, the power of this technique is limited by the syntax of
Python, since the code to be transformed must first be parsed under
normal Python syntax rules. Lisp doesn't suffer from this thanks to its
"everything is a list" syntax, but among other things it makes it very
awkward to create macros with code suites.

Suppose we wanted to make a stupid macro that just executes a code suite
twice. We could abuse Python syntax in various ways:

if +"twice":
while MACRO(twice):
for twice in MACROS():

....but we can't make the parser accept any of these nicer syntaxes:

MACRO(twice):
do twice:
@twice:

Even if Guido were willing to add syntactical support for macros, such
as "@macroname(<args>)" and "@macroname:<suite>", a problem remains.
While it is only moderately difficult to define an AST transformation
programmatically, I can't imagine what a nice inline template-ish way to
define macros, such as Lisp provides, would look like in Python.

Cheers,

Evan @ 4-am




Coby Beck 10-08-2003 09:57 AM

Re: Explanation of macros; Haskell macros
 
<mike420@ziplip.com> wrote in message
news:GGDUFQOGIAD5H2L3GDEKNREOMFKFLREPEFD3MGCP@zipl ip.com...
> I think others dropped the ball while trying to explain macros
> to non-Lispers recently. The examples I saw posted were either
> too difficult or easily doable without macros (maybe even easier).


Here's a nice example from some production code I wrote that is easy to
grok.

The purpose: a socket server excepts a set of specific commands from
clients. Client commands must be verified as allowed and the arguments
marshalled before applying the appropriate function to the arguments. I
wanted a clean way to express this and automate the writing of error
catching code.

Usage: define-server-cmd name (method-parameters) constraints code

Sample usage:
(define-server-cmd set-field-sequence ((session morph-configuration-session)
field-list)
((listp field-list)
(remove-if-not #'(lambda (key) (member key *logical-types*))
field-list))
(with-slots (client source-blueprint state) session
(setf (field-sequence (source-blueprint session)) field-list)
(setf state :blueprint-set)
(send session (write-to-string state))))

The resulting expansion:
(PROGN
(DEFMETHOD SET-FIELD-SEQUENCE
((SESSION MORPH-CONFIGURATION-SESSION) FIELD-LIST)
(WITH-SLOTS (CLIENT SOURCE-BLUEPRINT STATE)
SESSION
(SETF (FIELD-SEQUENCE (SOURCE-BLUEPRINT SESSION))
FIELD-LIST)
(SETF STATE :BLUEPRINT-SET)
(SEND SESSION (WRITE-TO-STRING STATE))))
(DEFMETHOD MARSHAL-ARGS-FOR-CMD
((CMD (EQL 'SET-FIELD-SEQUENCE))
(SESSION MORPH-CONFIGURATION-SESSION))
(LET (FIELD-LIST)
(PROGN
(SETF FIELD-LIST
(RECEIVE SESSION :TIMEOUT *COMMAND-PARAMETER-TIMEOUT*))
(UNLESS FIELD-LIST
(ERROR 'TIMEOUT-ERROR
:EXPECTATION
(FORMAT NIL "~A parameter to ~A command" 'FIELD-LIST CMD)
:TIMEOUT
*COMMAND-PARAMETER-TIMEOUT*)))
(UNLESS (LISTP FIELD-LIST)
(ERROR 'COMMAND-CONSTRAINT-VIOLATION
:CONSTRAINT
'(LISTP FIELD-LIST)
:COMMAND
CMD))
(UNLESS (REMOVE-IF-NOT #'(LAMBDA (KEY)
(MEMBER KEY *LOGICAL-TYPES*))
FIELD-LIST)
(ERROR 'COMMAND-CONSTRAINT-VIOLATION
:CONSTRAINT
'(REMOVE-IF-NOT #'(LAMBDA (KEY)
(MEMBER KEY *LOGICAL-TYPES*))
FIELD-LIST)
:COMMAND
CMD))
(LIST FIELD-LIST)))
(PUSHNEW 'SET-FIELD-SEQUENCE *CONFIG-SERVER-COMMANDS*))

Usage of what the macro gave me in context (some error handling noise
removed):

(defmethod run-config-command-loop ((session morph-configuration-session))
(let ((*package* (find-package :udt)))
(unwind-protect
(with-slots (client) session
(loop
(let (cmd)
(setf cmd (receive session :timeout *command-timeout* :eof-value
:eof))
(cond
((or (eq cmd :eof) (eq cmd :stop)) (return))
((member cmd *config-server-commands*)
(let ((cmd-args (marshal-args-for-cmd cmd session)))
(apply cmd session cmd-args)))
(t (execute-generic-command cmd client)))))

(send session "session loop terminated"))
(when (eq (state session) :finalized)
(setf *active-sessions* (delete session *active-sessions*))))))

The macro definition:

(defmacro define-server-cmd (name (session-specializer &rest args)
constraints &body body)
(let ((session-var (car session-specializer))
(session-class (cadr session-specializer)))
`(progn
(defmethod ,name ((,session-var ,session-class)
,@(mapcar #'(lambda (arg)
(if (symbolp arg) arg (car arg)))
args))
,@body)
(defmethod marshal-args-for-cmd
((cmd (eql ',name)) ,session-specializer)
(let (,@args)
,@(loop for var in args
collect
`(progn
(setf ,var (receive ,session-var
:timeout *command-parameter-timeout*))
(unless ,var
(error 'timeout-error
:expectation (format nil "~A parameter to ~A command"
',var cmd)
:timeout *command-parameter-timeout*))))
,@(loop for con in constraints
collect
`(unless ,con
(error 'command-constraint-violation
:constraint ',con
:command cmd)))
(list ,@args)))
(pushnew ',name *config-server-commands*))))

I think the advantages are tremendously obvious, and very satisfying to take
advantage of!

--
Coby Beck
(remove #\Space "coby 101 @ big pond . com")



Lex Spoon 10-24-2003 03:48 PM

Re: Explanation of macros; Haskell macros
 
This thread has seemed to miss the biggest advantage of macros. Most
examples so far are cases where the macro buys you faster code. This
is not extremely exciting, IMHO, because a better compiler can often
accomplish the things that are described. If you care that much about
performance then surely you want to be using a good optimizing
compiler, and a good compiler will surely know about, e.g., for loops
that go across constant ranges. I tend to think of macros as bad
style for this kind of thing, unless it's very clearly a performance
problem you are having. Macros often look like functions, but they
don't act like them, and it's very easy to introduce bugs with them.

A much bigger advantage of macros is that you can literally extend
the language. For example, it is nice to be able to type something
like this:

(regex (| h H) "ello, " (| w W) orld (? "!"))


To allow this in the nicest way requires using a macro for 'regex.
Without macros, you can still do it:

(regex '(| h H) "ello, " (| w W) orld (? "!"))

but now this guy is going to get compiled at runtime, and you get into
the issue of trying to save the compiled regex somewhere ahead of
time. And while regexes themselves compile pretty quickly, suppose
it's something more like:

(parser
(grammer
;;... a BNF grammer ...



I also loved the example of both printing a statement and executing
it, which is extremely useful for assertion checking. You can't
really do this properly without macros.



-Lex


All times are GMT. The time now is 09:10 AM.

Powered by vBulletin®. Copyright ©2000 - 2013, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57