Explanation of macros; Haskell macros
Coby Beck
cbeck at mercury.bc.ca
Tue Nov 4 01:27:21 EST 2003
"Peter Seibel" <peter at javamonkey.com> wrote in message
news:m3ptg8em7s.fsf at javamonkey.com...
> stephen at dino.dnsalias.com (Stephen J. Bevan) writes:
> I'm about ready to give up on trying to evangelize macros on
> usenet--if you show a simple macro to demonstrate a point people say:
> "That's silly, who'd want to do that?" If you show a macro that allows
> some simple code to expand into some complex code people say, "I don't
> understand that code it expands into, show me a simpler example."
Yes, it is always a moving target...
I posted this way way upthread but it only made it to one of the
cross-posted groups so I'll try again:
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))
, at body)
(defmethod marshal-args-for-cmd
((cmd (eql ',name)) ,session-specializer)
(let (, at 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 , at args)))
(pushnew ',name *config-server-commands*))))
I think the advantages are tremendously obvious, and very satisfying to make
use of!
--
Coby Beck
(remove #\Space "coby 101 @ big pond . com")
More information about the Python-list
mailing list