![]() |
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? |
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 |
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. |
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 |
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 |
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 |
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 |
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 |
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") |
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.