Explanation of macros; Haskell macros

M

mike420

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?
 
T

Tomasz Zielonka

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
 
P

prunesquallor

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.
 
T

Terry Reedy

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
 
K

Kenny Tilton

I think others dropped the ball while trying to explain macros
to non-Lispers recently.

.... said:
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
 
R

Rolf Wester

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
 
R

Richie Hindle

[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.
 
E

Evan Simpson

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
 
C

Coby Beck

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!
 
L

Lex Spoon

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
 
R

Robert Klemme

Lex Spoon said:
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
 
A

Alain Picard

Robert Klemme said:
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.
 
L

Lex Spoon

Robert Klemme said:
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
 
J

Joachim Durchholz

Alain said:
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
 
A

Alain Picard

Joachim Durchholz said:
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.]
 
J

Joachim Durchholz

Alain said:
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
 
J

Joachim Durchholz

Roman said:
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:
Agreed.

and [quoting will lead to] less intuitive code.[/QUOTE]

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

Regards,
Jo
 
R

Roman Belenov

Joachim Durchholz said:
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.
 
P

Peter Seibel

Joachim Durchholz said:
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
 
J

Joachim Durchholz

Peter said:
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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,534
Members
45,007
Latest member
OrderFitnessKetoCapsules

Latest Threads

Top