Explanation of macros; Haskell macros

P

Peter Seibel

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

Uh right, that's what macros are for, providing the syntactic sugar.

-Peter
 
A

Anton van Straaten

Peter said:
sugar.

Uh right, that's what macros are for, providing the syntactic sugar.

Yes, but the point is that with a concise syntax for lambda, entire classes
of macros can become unnecessary. That's how Smalltalk handles 'if', for
example - no macros or special forms needed.
 
L

Lex Spoon

Joachim Durchholz said:
This statement is wrong if left in full generality: higher-order
functions can control quite precisely what gets evaluated when.

They don't let you execute stuff at compile time. If you write down:

(parser '( ;; ... 10 pages of BNF ... ;;
))


Then the parser is going to be computed at runtime. HOF themselves
don't help you compute stuff at compile time. (Though one can
certainly imagine a language where compile-time execution is
controlled by pragmas.)


-Lex
 
P

Peter Seibel

Anton van Straaten said:
Yes, but the point is that with a concise syntax for lambda, entire
classes of macros can become unnecessary. That's how Smalltalk
handles 'if', for example - no macros or special forms needed.

Okay, so I picked an unfortunate example in that it also falls in the
class of macros that become unecessary when other bits of syntactic
sugar are provided. How about this one.

Which is more intuitive?

This:

(defun foo () (print "hello, world")))

Or this:

(progn (eval-when :)compile-toplevel)
(excl::check-lock-definitions-compile-time 'foo 'function
'defun (fboundp 'foo))
(push 'foo excl::.functions-defined.))
(progn (eval-when :)compile-toplevel)
(excl::check-de-argdecl-change 'foo 'nil))
(declaim (excl::dynamic-extent-arguments nil foo)))
(setf (fdefinition 'foo)
(let ((excl::f
(named-function foo
(lambda nil
(block foo (print "hello, world"))))))
(excl::set-func_name excl::f 'foo)
excl::f))
(remprop 'foo 'excl::%fun-documentation)
(record-source-file 'foo) 'foo)

-Peter
 
M

Marcin 'Qrczak' Kowalczyk

Okay, so I picked an unfortunate example in that it also falls in the
class of macros that become unecessary when other bits of syntactic
sugar are provided. How about this one.
[...]

Nobody says that *everything* should be expressed by higher order
functions. But with enough builtin syntax and concise anonymous function
notation (in particular using {} or [] for functions without arguments)
there are much fewer uses of macros than in Lisp, which has little builtin
syntax and ugly anonymous functions.

The remaining uses of macros are so rare that few languages choose to take
the cost of having macros (I mean full-blown Lisp-like macros which execute
arbitrary code at compile time and can examine its parameters).

The costs: syntax seems to be constrained to have a very regular surface
structure full of parentheses; the language must describe and implement a
representation of code as data which would otherwise be unnecessary; every
compiler must include an interpreter; it's hard to report errors to the
user showing the code as it's written in the source if the error is found
after macro expansion.

I'm not saying that the cost is obviously too high but that it's a
tradeoff, macros don't come for free. And that there are other ideas which
can replace many macros with different tradeoffs: namely a richer builtin
syntax and concise anonymous functions. In a lazy language there are yet
fewer uses of macros.
 
P

Pascal Costanza

Peter said:
Okay, so I picked an unfortunate example in that it also falls in the
class of macros that become unecessary when other bits of syntactic
sugar are provided.

No, I don't think so. Here is what I needed in a real program.

I had several cond statements at various places of my code. They all
didn't have a default case. At a certain stage, I needed to be sure to
get an exceptional return value for the default case instead of NIL. So
I added a macro:

(defconst *error-form*
'(mv (trans-pstate pstate :exception 'program-error) *void*))

(defmacro ev-cond* (pstate &body body)
`(cond ,@(append body `((t (let ((pstate ,pstate)) ,*error-form*))))))

*ERROR-FORM* here is the standard code that changes a program state to a
state with an exception and a *void* return type. EV-COND* is my
modified COND that simply adds a default case in which PSTATE is
correctly bound so that *ERROR-FORM* can do the right thing.

(This isn't "pure" Common Lisp, but ACL2, and the code is somewhat more
complicated than necessary because ACL2 places some severe restrictions
on the code that one can write.)

The essence of this is as follows:

(defmacro my-cond (&body body)
`(cond ,@(append body '(t (handle-the-default-case))))

With simple shadowing imports one can make this completely transparent
to client code.

How would you do this with a Smalltalk-like if expression? (This is not
a rhetoric question - I would be really interested to hear how one could
make this work.)


Pascal
 
P

Pascal Costanza

Marcin said:
I'm not saying that the cost is obviously too high but that it's a
tradeoff, macros don't come for free. And that there are other ideas which
can replace many macros with different tradeoffs: namely a richer builtin
syntax and concise anonymous functions. In a lazy language there are yet
fewer uses of macros.

Ahh, the "in 99% of all cases" argument once again...


Pascal
 
P

Peter Seibel

Marcin 'Qrczak' Kowalczyk said:
The remaining uses of macros are so rare ...

So, just in case it wasn't completely clear to anyone watching, this
right here is the big point of disagreement. What do you think the
"remaining uses" are and why do you say they are "rare"?[1]

Common Lispers don't consider the remaining uses we make of
macros--things like defining functions, classes, and methods, our
condition (exception) system, interfacing to C code, etc. to be all
that rare.

And that's just the stuff that's built into the language (or a
particular implementation in the case of interfacing to C). Now let's
look at a sample of the code in my personal code library: test
frameworks, regular expressions, parser generators, HTML generation,
PDF generation and typesetting, genetic programming system ... hmmm,
they all seem to use macros, some of them are practically nothing
*but* macros.

Of course in languages that don't have macros, these things are done
without macros--they are either built into the language (defining
functions) or moved out into completely external tools (parser
generators). But--as we should all recognize by now--Turing
equivalence has nothing to do with language expressiveness.

-Peter

[1] Even if the uses of macros *were* rare that's doesn't seem to be
the correct metric to balance against the "costs" of macros. Normally
we weigh costs versus benefits, right? So in a way, the higher
non-Lispers estimate the "costs" of macros (need for an code-as-data
syntax, interpreter built into the compiler, etc.), the higher they
should estimate the benefits. At least if they give Lisp programmers
*any* credit at all for making a rational choice in tool selection.
Because Common Lispers love macros and find the benefits *far*
outweigh the costs. But I guess you can explain that by assuming we're
deluded.
 
S

Simon Taylor

[1] Even if the uses of macros *were* rare that's doesn't seem to be
the correct metric to balance against the "costs" of macros. Normally
we weigh costs versus benefits, right? So in a way, the higher
non-Lispers estimate the "costs" of macros (need for an code-as-data
syntax, interpreter built into the compiler, etc.), the higher they
should estimate the benefits. At least if they give Lisp programmers
*any* credit at all for making a rational choice in tool selection.
Because Common Lispers love macros and find the benefits *far*
outweigh the costs. But I guess you can explain that by assuming we're
deluded.

No one is saying all Lispers are deluded (I hope). There are plenty
of good ideas in Lisp, but I'd like to have them without losing the
advantages of static typing and syntax requiring minimal editor support.
Template Haskell is a good step in that direction.

Simon.
 
C

Coby Beck

Pascal Costanza said:
The essence of this is as follows:

(defmacro my-cond (&body body)
`(cond ,@(append body '(t (handle-the-default-case))))

Just to provide a more apparently general (and working ;) version, analogous
to CL's ECASE:

CL-USER 90 >
(defmacro econd (&body body)
`(cond ,@(append body
`((t (error (format nil
"fell through ECOND form. could not
satisfy any of the following: ~{~%~A~}~%"
(mapcar #'(lambda (cond)
(car cond))
',body))))))))
ECOND

CL-USER 91 > (econd
((= 3 4) "foo")
((= 4 4) "bar"))
"bar"

CL-USER 92 > (econd
((= 3 4) "foo")
((= 4 5) "bar"))

Error: fell through ECOND form. could not satisfy any of the following:
(= 3 4)
(= 4 5)

1 (abort) Return to level 0.
2 Return to top loop level 0.

Type :b for backtrace, :c <option number> to proceed, or :? for other
options
 
D

Dirk Thierbach

Since the overhead of evaluating it at runtime is minimal, especially
with lazyness, that's exactly the situation where it is natural to use
a HOF instead of a macro.

I didn't go through the two proposed Lisp solutions in detail, but here's
a HOF in Haskell that does the same. First a general 'cond' with an
explicit default case, then 'econd' based on cond with an error as
default case:

cond :: a -> [(Bool, a)] -> a
cond def [] = def
cond def ((True,x):_) = x
cond def ((False,_):xs) = econd xs

econd :: [(Bool, a)] -> a
econd = cond (error "Default econd case")
CL-USER 91 > (econd
((= 3 4) "foo")
((= 4 4) "bar"))
"bar"

CL-USER 92 > (econd
((= 3 4) "foo")
((= 4 5) "bar"))

Error: fell through ECOND form.

GHC Interactive, version 5.02.2, for Haskell 98.

Main> econd [(3 == 4, "foo"), (4 == 4, "bar")]
"bar"
Main> econd [(3 == 4, "foo"), (4 == 5, "bar")]
*** Exception: Default econd case


I have made the recursion of 'cond' explicit, but of course 'cond' can
be defined by a fold (and partial application):

cond' = foldr (\(guard,val) def -> if guard then val else def)


You DON'T need macros for such things. You don't need dynamic typing,
either.

- Dirk
 
R

Roman Belenov

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

IMHO it's not that simple - quoting is there to allow insertion of
literal data into program. It's not easy (and not always possible) to
deduce in compile time that some quoted expression will be used as a
code, especially if it is passed to other functions, stored in complex
data structures etc. before evaluation.
and [quoting will lead to] less intuitive code.

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

I meant quoting in Common Lisp - due to the absence of access to
lexicals data exchange between quoted and normal code is not
straightforward. If quoted and unquoted code were treated equally, it
wouldn't be a problem.
 
O

Olaf Klischat

Coby Beck said:
Just to provide a more apparently general (and working ;) version, analogous
to CL's ECASE:

CL-USER 90 >
(defmacro econd (&body body)
`(cond ,@(append body
`((t (error (format nil
"fell through ECOND form. could not
satisfy any of the following: ~{~%~A~}~%"
(mapcar #'(lambda (cond)
(car cond))
',body))))))))


Just for the record: Is the "append" stuff necessary? Why not write
this as

(defmacro econd (&body body)
`(cond ,@body
(t (error (format nil "fell through ECOND form. could not satisfy any of the following: ~{~%~A~}~%"
',(mapcar #'(lambda (cond)
(car cond))
body))))))

?

Olaf
 
P

Pascal Costanza

Dirk said:
Since the overhead of evaluating it at runtime is minimal, especially
with lazyness, that's exactly the situation where it is natural to use
a HOF instead of a macro.

OK, nice solution.
I didn't go through the two proposed Lisp solutions in detail, but here's
a HOF in Haskell that does the same. First a general 'cond' with an
explicit default case, then 'econd' based on cond with an error as
default case:

cond :: a -> [(Bool, a)] -> a
cond def [] = def
cond def ((True,x):_) = x
cond def ((False,_):xs) = econd xs
^^^^^^^^

I guess this is a typo - it should be "cond xs", right?
econd :: [(Bool, a)] -> a
econd = cond (error "Default econd case")

Hmm, what if I had wanted to add a default check in front of each cond,
instead of at the end?

Also, Coby's version prints a useful error message that mentions all
conditions ("could not satisfy..."):
CL-USER 91 > (econd
((= 3 4) "foo")
((= 4 4) "bar"))
"bar"

CL-USER 92 > (econd
((= 3 4) "foo")
((= 4 5) "bar"))

Error: fell through ECOND form. could not satisfy any of the following:
(= 3 4)
(= 4 5)

Would it be possible to add such a message with your proposed technique?


Pascal
 
J

Joachim Durchholz

Lex said:
They don't let you execute stuff at compile time.

Ah, I didn't think of compile-time evaluation, I was thinking about
evaluation stuff sooner or later at run-time.

And, of course, macros can evaluate at compile time.
Personally, I'd prefer to do compile-time evaluation based on "the
compiler will evaluate all known-to-be-constant expressions". The
advantage here is that programmers don't need to learn another
sublanguage for compile-time expressions.

I haven't seen this done in full generality though, so I don't know how
well this would work in practice.

Regards,
Jo
 
P

Peter Seibel

Joachim Durchholz said:
And, of course, macros can evaluate at compile time. Personally, I'd
prefer to do compile-time evaluation based on "the compiler will
evaluate all known-to-be-constant expressions". The advantage here
is that programmers don't need to learn another sublanguage for
compile-time expressions.

Ah, but in Lisp we don't have to. We use Lisp.

-Peter
 
J

Joachim Durchholz

Peter said:
Ah, but in Lisp we don't have to. We use Lisp.

Having readers and special forms /is/ an extra sublanguage. I don't have
to learn extra syntax for these forms (which is good), but I do have to
learn about a lot of special rules that apply to macros and nothing else
(which is not so good).

Letting the compiler evaluate what it can means that I don't even have
to learn extra forms.

Actually, that's one of the reasons that keeps my from trying out a
modern Lisp: I'd have to learn all these extra forms, and I've got a
feeling that macrology à la Lisp is oversophisticated for something as
simple as compile-time evaluation.

I'm pretty sure that macros solve more problems than just compile-time
evaluation. I just suspect that better solutions are available in every
case, and I not just suspect but know that macros have some very serious
disadvantages (such as bad debugger interaction, a potential for really
ugly hairballs, and a constant temptation for stopgap solutions that
"work well enough").
Lisp-the-language is a quite pretty lean-and-mean KISS language. The
stuff that's built on top of it (macros, readers, dispatch mechanisms,
etc. etc.) is neither lean nor KISS nor (IMHO) pretty - YMMV. Or, more
to the point: I have yet to see something that cannot be done in a
leaner, more KISS way.
Which is why I'm going to stick with functional languages. After all,
the higher-order stuff was what attracted me to Lisp in the first place,
the rest of the language less than impressed me. I'll grant that modern
Lisps have found ways around any problems (otherwise, modern Lisps
wouldn't be in use), but why mess with workarounds if I can have the
cake (higher-order programming) and eat it, too (amenability to static
analysis)?

Regards,
Jo
 
K

Kenny Tilton

Joachim said:
Lisp-the-language is a quite pretty lean-and-mean KISS language. The
stuff that's built on top of it (macros, readers, dispatch mechanisms,
etc. etc.) is neither lean nor KISS

Do what I did (well, not /you/, but someone else might): don't use fun
stuff until you are ready for it. Took me a couple of weeks before I
started writing macros, longer before I used the MOP, longer still
before nifty use of special variables and then symbol macros. etc etc.
The thing is, as a developer of serious applications, I appreciate and
make good use of just about everything in CL.

I would so totally not like to have to reinvent all that stuff myself
--I have applications to write!-- or end up with a kenny-specific
environment so no one else could ever run my code.

Gotta go do my first read macro...

:)

kenny

--
http://tilton-technology.com

Why Lisp? http://alu.cliki.net/RtL Highlight Film

Your Project Here! http://alu.cliki.net/Industry Application
 
P

Peter Seibel

Joachim Durchholz said:
Having readers and special forms /is/ an extra sublanguage. I don't
have to learn extra syntax for these forms (which is good), but I do
have to learn about a lot of special rules that apply to macros and
nothing else (which is not so good).

Hmmmm. The special forms (25 of them, called special operators these
days, by the by) are used the same in macros and functions. Lisp's
customizable reader is a separate thing--there is no need to customize
the reader to write macros.
Letting the compiler evaluate what it can means that I don't even
have to learn extra forms.

I'm not sure what "extra" forms you're talking about. Other than
DEFMACRO, I guess. But by that argument we'd be better off without
DEFUN too because that's just another darn thing to learn.
Actually, that's one of the reasons that keeps my from trying out a
modern Lisp: I'd have to learn all these extra forms, and I've got a
feeling that macrology à la Lisp is oversophisticated for something
as simple as compile-time evaluation.

Yes, it probably would be over kill if that was all it was for.
I'm pretty sure that macros solve more problems than just
compile-time evaluation.
Yup.

I just suspect that better solutions are available in every case,

Interesting. A lot of people suspect that who haven't actually used
Common Lisp macros. Yet almost all Common Lispers--who by in large are
*not* monolinguists--think macros are one of Common Lisp's great
features. I'm not saying your wrong, but if those better solutions are
out there for all the things I can do with macros, I haven't seen
them. Now I don't know Haskell or ML so I'm also suffering from finite
knowledge. Maybe one day I'll have time to learn one of them for
myself and see if they really do offer better solutions.
and I not just suspect but know that macros have some very serious
disadvantages (such as bad debugger interaction, a potential for
really ugly hairballs, and a constant temptation for stopgap
solutions that "work well enough").

Well, of those the debugger interaction is perhaps the most serious.
Yet in practice (Hey Pascal, I almost said "in 99% of cases"!) it
doesn't seem to be that much of a problem. Maybe that's because we've
just learned to deal with the pain; maybe MACROEXPAND is all you
really need to get your bearings. At any rate, there's no in principle
that a Lisp implementation couldn't keep track of macro information
along with the compiled code just the way most compiler keep track of
line number information in order to show you the code as written in
the debugger. (And if it was really slick to let you step through the
macro expansion, etc.)
Lisp-the-language is a quite pretty lean-and-mean KISS language. The
stuff that's built on top of it (macros, readers, dispatch
mechanisms, etc. etc.) is neither lean nor KISS nor (IMHO) pretty -
YMMV.

Clearly. I find Common Lisp to be a pretty beautiful piece of
*engineering*. Which may be different than a beautiful realization of
a beautiful theory.
Or, more to the point: I have yet to see something that cannot be
done in a leaner, more KISS way.

Well, if I promise to continue to think that someday I really should
learn a hard-core FP language so I can see what all the static typing
fuss is about, will you promise to think in the back of your mind that
maybe someday you should learn Common Lisp and see what makes us all
so gaga over macros.
Which is why I'm going to stick with functional languages. After
all, the higher-order stuff was what attracted me to Lisp in the
first place, the rest of the language less than impressed me. I'll
grant that modern Lisps have found ways around any problems
(otherwise, modern Lisps wouldn't be in use), but why mess with
workarounds if I can have the cake (higher-order programming) and
eat it, too (amenability to static analysis)?

Right on. If that's the flavor of cake--enjoy it.

-Peter
 
J

Jesse Tov

Peter Seibel said:
them. Now I don't know Haskell or ML so I'm also suffering from finite
knowledge. Maybe one day I'll have time to learn one of them for
myself and see if they really do offer better solutions.

The existence of Template Haskell indicates to me that there are things
people want to do with macros that aren't reasonably done in Haskell. I
don't even know if I've ever wanted to do these things, because people
rarely miss language features that they don't know.

Jesse
 

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

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top