Explanation of macros; Haskell macros

Discussion in 'Python' started by mike420@ziplip.com, Oct 7, 2003.

  1. Guest

    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?
     
    , Oct 7, 2003
    #1
    1. Advertising

  2. 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
     
    Tomasz Zielonka, Oct 7, 2003
    #2
    1. Advertising

  3. Guest

    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.
     
    , Oct 7, 2003
    #3
  4. Terry Reedy Guest

    <> wrote in message
    news:...
    > 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
     
    Terry Reedy, Oct 7, 2003
    #4
  5. Kenny Tilton Guest

    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
     
    Kenny Tilton, Oct 7, 2003
    #5
  6. Rolf Wester Guest

    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
     
    Rolf Wester, Oct 7, 2003
    #6
  7. [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 Hindle, Oct 7, 2003
    #7
  8. Evan Simpson Guest

    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
     
    Evan Simpson, Oct 7, 2003
    #8
  9. Coby Beck Guest

    <> wrote in message
    news:...
    > 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")
     
    Coby Beck, Oct 8, 2003
    #9
  10. Lex Spoon Guest

    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
     
    Lex Spoon, Oct 24, 2003
    #10
  11. "Lex Spoon" <> schrieb im Newsbeitrag
    news:...
    > 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.


    Just for the sake of my understanding: could one summarize this as "macros
    make control of *when* something is evaluated easier"? I mean, marco
    arguments are not evaluated before the macro "call" as opposed to function
    arguments. With that, you can decide inside the macro, how you treat
    those arguments and if you evaluate them at all. Thx!

    Regards

    robert
     
    Robert Klemme, Oct 27, 2003
    #11
  12. Alain Picard Guest

    "Robert Klemme" <> writes:

    > Just for the sake of my understanding: could one summarize this as "macros
    > make control of *when* something is evaluated easier"?


    More precisely: "macros make control of when, if, and how often,
    something is evaluated _possible_."

    Functions (your only other option) give you no choice.

    But, yeah, you've got it.

    --
    It would be difficult to construe Larry Wall, in article
    this as a feature. <>
     
    Alain Picard, Oct 27, 2003
    #12
  13. Lex Spoon Guest

    "Robert Klemme" <> writes:
    > Just for the sake of my understanding: could one summarize this as "macros
    > make control of *when* something is evaluated easier"? I mean, marco
    > arguments are not evaluated before the macro "call" as opposed to function
    > arguments. With that, you can decide inside the macro, how you treat
    > those arguments and if you evaluate them at all. Thx!



    Yes, that's one big advantage. If you are willing to do everything at
    runtime, then HOF plus a nice general syntax for literals
    (e.g. s-expressions) give you the same thing.


    -Lex
     
    Lex Spoon, Oct 28, 2003
    #13
  14. Alain Picard wrote:

    > "Robert Klemme" <> writes:
    >
    >>Just for the sake of my understanding: could one summarize this as "macros
    >>make control of *when* something is evaluated easier"?

    >
    > More precisely: "macros make control of when, if, and how often,
    > something is evaluated _possible_."
    >
    > Functions (your only other option) give you no choice.


    This statement is wrong if left in full generality: higher-order
    functions can control quite precisely what gets evaluated when. If the
    language offers both lazy and strict parameters, evaluation time control
    extends to the parameter expressions as well.
    It's a different kind of control than macros, of course. (It would be
    interesting to see how things would map from macros to functions and
    vice versa.)

    Regards,
    Jo
     
    Joachim Durchholz, Oct 30, 2003
    #14
  15. Alain Picard Guest

    Joachim Durchholz <> writes:

    > Alain Picard wrote:
    >
    >> More precisely: "macros make control of when, if, and how often,
    >> something is evaluated _possible_." Functions
    >> (your only other option) give you no choice.

    >
    > This statement is wrong if left in full generality: higher-order
    > functions can control quite precisely what gets evaluated when. If the
    > language offers both lazy and strict parameters, evaluation time
    > control extends to the parameter expressions as well.


    My apologies to the functional guys. I was responding from a lisp
    newsgroup: so let me amend the above to end with "IN LISP".
    [We do not have lazy evaluation by default.]
     
    Alain Picard, Oct 30, 2003
    #15
  16. Alain Picard wrote:
    > Joachim Durchholz <> writes:
    >>Alain Picard wrote:
    >>>More precisely: "macros make control of when, if, and how often,
    >>> something is evaluated _possible_." Functions
    >>>(your only other option) give you no choice.

    >>
    >>This statement is wrong if left in full generality: higher-order
    >>functions can control quite precisely what gets evaluated when. If the
    >>language offers both lazy and strict parameters, evaluation time
    >>control extends to the parameter expressions as well.

    >
    > My apologies to the functional guys. I was responding from a lisp
    > newsgroup:


    Ah, the joys of inadvertent crossposting ;-)

    > so let me amend the above to end with "IN LISP".


    IIRC even Lisp allows you to keep expressions unevaluated via quoting.
    So macros wouldn't be the only way to control evaluation: quote the
    expression and have the callee evaluate it at a convenient time.

    > [We do not have lazy evaluation by default.]


    The default evaluation strategy doesn't change too much; all you need is
    some way of deferred evaluation. It need not even be laziness, having a
    meta level (such as quoting) or access to the compiler can be used.

    As I said, it's still different from macros.

    Regards,
    Jo
     
    Joachim Durchholz, Oct 30, 2003
    #16
  17. Roman Belenov wrote:
    > Joachim Durchholz <> writes:
    >
    >>IIRC even Lisp allows you to keep expressions unevaluated via
    >>quoting. So macros wouldn't be the only way to control evaluation:
    >>quote the expression and have the callee evaluate it at a convenient
    >>time.

    >
    >
    > The problem is that quoted form is just a piece of data from
    > compiler's point of view, so it is kept as is and has to be interpeted
    > in runtime (while code generated by macros is usually compiled
    > normally);


    If that's the case, it's probably more due to lack of demand than due to
    serious technical issues.

    > besides, such forms can not access lexical variables in the
    > enclosing context (otherwise runtime environment would have to support
    > access to lexicals by name, which has performance implications and is
    > not necessary for normal compiled code). So, theoretically, you can
    > use quoting to control the order of evaluation, but practically it
    > will lead to very inefficient
    Code:
    [/color]
    
    Agreed.
    [color=blue]
    > and [quoting will lead to] less intuitive code.[/color]
    
    Efficiency issues aside: how are macros more intuitive than quoting?
    
    Regards,
    Jo
     
    Joachim Durchholz, Oct 30, 2003
    #17
  18. Joachim Durchholz <> writes:

    > IIRC even Lisp allows you to keep expressions unevaluated via
    > quoting. So macros wouldn't be the only way to control evaluation:
    > quote the expression and have the callee evaluate it at a convenient
    > time.


    The problem is that quoted form is just a piece of data from
    compiler's point of view, so it is kept as is and has to be interpeted
    in runtime (while code generated by macros is usually compiled
    normally); besides, such forms can not access lexical variables in the
    enclosing context (otherwise runtime environment would have to support
    access to lexicals by name, which has performance implications and is
    not necessary for normal compiled code). So, theoretically, you can
    use quoting to control the order of evaluation, but practically it
    will lead to very inefficient and less intuitive code.

    --
    With regards, Roman.

    Standard disclaimer: I work for them, but I don't speak for them.
     
    Roman Belenov, Oct 30, 2003
    #18
  19. Peter Seibel Guest

    Joachim Durchholz <> writes:

    > Efficiency issues aside: how are macros more intuitive than quoting?


    Because they let you write what you intuitively expect (assuming your
    intuitions are tuned a particular way, I suppose). In Common Lisp,
    WHEN is a macro that lets you write this:

    (when (> x y) (print x))

    If you tried to write it as a function using quoting to prevent
    evaluation you might try this:

    (when (> x y) '(print x))

    which won't work in general because of the iniability for the code
    inside WHEN to evaluate the data '(print x) in the lexical environment
    where it appears.

    Or you could do this:

    (when (> x y) #'(lambda () (print x)))

    which could work but seems a bit convoluted (i.e. unintuitive)
    compared to the macro version.

    -Peter

    --
    Peter Seibel

    Lisp is the red pill. -- John Fraser, comp.lang.lisp
     
    Peter Seibel, Oct 30, 2003
    #19
  20. Peter Seibel wrote:
    > Joachim Durchholz <> writes:
    >
    >
    >>Efficiency issues aside: how are macros more intuitive than quoting?

    >
    > Or you could do this:
    >
    > (when (> x y) #'(lambda () (print x)))
    >
    > which could work but seems a bit convoluted (i.e. unintuitive)
    > compared to the macro version.


    Um, right, but that's just a question of having the right syntactic sugar.

    Regards,
    Jo
     
    Joachim Durchholz, Oct 30, 2003
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    1
    Views:
    476
    Marco Antoniotti
    Oct 7, 2003
  2. Replies:
    5
    Views:
    511
  3. Michael T. Babcock

    Re: Explanation of macros; Haskell macros

    Michael T. Babcock, Nov 3, 2003, in forum: Python
    Replies:
    0
    Views:
    534
    Michael T. Babcock
    Nov 3, 2003
  4. Casey Hawthorne
    Replies:
    3
    Views:
    348
  5. Alia Khouri
    Replies:
    0
    Views:
    250
    Alia Khouri
    Oct 31, 2010
Loading...

Share This Page