
What Template Haskell gets wrong and Racket gets right - adamnemecek
http://blog.ezyang.com/2016/07/what-template-haskell-gets-wrong-and-racket-gets-right/
======
dleslie
FYI, Racket's syntax macros is from its Scheme roots. For further information
on what Scheme provides, by standard revision:

R5RS: [0]

R6RS: [1]

R7RS: [2] (Page 22)

And many Schemes provide non-standard extensions; for instance, Chicken
supports non-hygienic macros[3].

FWIW, I prefer to think of the two stages as "syntax expansion" and
"evaluation", and not "compile time" and "run time". Compilation and running
are loaded terms that don't appropriately capture the totality of situations
where this behaviour applies, in Scheme.

0: [http://schemers.org/Documents/Standards/R5RS/HTML/r5rs-
Z-H-8...](http://schemers.org/Documents/Standards/R5RS/HTML/r5rs-
Z-H-8.html#%_sec_5.3)

1: [http://www.r6rs.org/final/html/r6rs/r6rs-
Z-H-14.html#node_se...](http://www.r6rs.org/final/html/r6rs/r6rs-
Z-H-14.html#node_sec_11.2.2)

2: [http://trac.sacrideo.us/wg/raw-
attachment/wiki/WikiStart/r7r...](http://trac.sacrideo.us/wg/raw-
attachment/wiki/WikiStart/r7rs.pdf)

3: [http://wiki.call-cc.org/explicit-renaming-macros](http://wiki.call-
cc.org/explicit-renaming-macros)

~~~
hellofunk
What exactly is the difference between Racket and Scheme, how are they
related?

~~~
ioddly
Racket is derived from Scheme. Sometime after R6RS I believe they decided to
just call their own not-necessarily compatible language Racket, but Racket can
also run RxRS scheme code.

[http://racket-lang.org/new-name.html](http://racket-lang.org/new-name.html)

------
kccqzy
Maybe this is heretical among Haskell lovers, but my personal, rather
undeveloped, feeling is that Haskell syntax is too complicated for macros.
Sure, Haskell code feels natural to read, but in fact they had a lot of
"superficial complexity"[1], i.e. the syntax supports many ways of expressing
the same thing. This makes any sorts of introspection at compile-time
troublesome. Compile-time code generation is less of a problem but still it
makes the experience less pleasant.

[1]: Hudak P., Hughes J., Peyton Jones, S., Wadler P. (2007). _A History of
Haskell: Being Lazy With Class._

~~~
nilved
That's not heretical, most Haskellers agree that macros are a misfeature
because (a) they are only useful in homoiconic languages (b) they are only
useful in strict languages (c) they interfere with type safety and code
clarity.

~~~
kenko
Why would they only be useful in strict languages? You don't need a macro to
write `unless` in Haskell, but you do to (say) automatically generate
typeclass membership given a datatype, or autogenerate lenses, and that use is
orthogonal to evaluation strategy.

~~~
jwhite
I guess parent is just saying that macros allow programmer control of
evaluation order, which you can't get otherwise in a strict language, but you
can with laziness.

The point could have been worded better I think.

------
alphonse23
I mentioned it in the comments of Edward Yang's article but I'll mention it
here again, Template Haskell is based on C++ templates, here's the original
paper [http://research.microsoft.com/en-
us/um/people/simonpj/papers...](http://research.microsoft.com/en-
us/um/people/simonpj/papers/meta-haskell/meta-haskell.pdf). Haskell was
written to mimic C++ more than lisp, at least when it comes to meta
programming. And if you read the paper having the compiler check the template
code before the expansion is there by design decision, that's why it's compile
time only.

~~~
naasking
> Haskell was written to mimic C++ more than lisp, at least when it comes to
> meta programming.

Depends what you mean by "metaprogramming". Most people would consider type
class "abuse" metaprogramming, but it's definitely not something inspired by
C++.

~~~
alphonse23
The wikipedia definition is good: "Metaprogramming is the writing of computer
programs with the ability to treat programs as their data. It means that a
program could be designed to read, generate, analyse or transform other
programs, and even modify itself while running."

In my own words, code that can read and write code (whether that's at compile
time or run time).

~~~
naasking
Right, so then Haskell type classes qualify, and my original point stands.
Certainly the type class language is limited, but common extensions make it
Turing complete [1].

[1]
[https://mail.haskell.org/pipermail/haskell/2006-August/01835...](https://mail.haskell.org/pipermail/haskell/2006-August/018355.html)

------
codemac
If you're interesting in learning more about syntax-case in general, I've been
learning more and FameInducedApathy on reddit sent me the following resources
that I have found very useful:

Macro By Example: ftp://www.cs.indiana.edu/pub/techreports/TR206.pdf

Dybvig's classic paper:
[https://www.cs.indiana.edu/%7Edyb/pubs/tr356.pdf](https://www.cs.indiana.edu/%7Edyb/pubs/tr356.pdf)

~~~
groovy2shoes
Great resources. I'd like to add "Syntactic Abstraction: The syntax-case
Expander" ([http://www.cs.indiana.edu/~dyb/pubs/bc-syntax-
case.pdf](http://www.cs.indiana.edu/~dyb/pubs/bc-syntax-case.pdf)), which
gives a high-level overview of how syntax-case may be implemented.

I'd also like to point out that, contrary to popular belief (for some
bewildering reason), syntax-case _is_ capable of expressing non-hygienic
macros via the `datum->syntax` function, which takes an identifier and a
symbol, and returns a new identifier with the same name as the symbol and the
same scope as the provided identifier. The R6RS even gives an example of
writing a completely unhygienic, Common Lisp-like macro transformer via
syntax-case (section 12.6 in the R6RS Libraries document [0]).

syntax-case has a bad rap, but it's really not a difficult system to learn,
and it's incredibly powerful. In particular, I think its "hygiene by default,
with per-identifier hygiene-breaking when and where you want it" is the best
possible policy for a macro system (as far as I'm aware, the only other macro
system with the same policy, though imo with a much more intuitive interface,
is Chicken's ir-macro-transformer for implicit-renaming macros [1]). The
combination of syntactic pattern-matching and procedural expansion is
extremely convenient, too: you never need to have a massive `let` form that
binds the car, cadr, caddr, cadddr, ... of the input form because the patterns
let you destructure and switch (as in case analysis, thus syntax- _case_ ) on
the input form much as in ML-style pattern matching. Of course, you could
argue that the pattern matching ought to be disjoint from the notion of a
macro transformer, and I'm inclined to agree, but my understanding is that
implementing it the way it is makes it easier to keep track of which variables
are "pattern variables" which helps to provide hygiene (someone please correct
me if I'm wrong).

Whenever a new language comes out with an unhygienic macro system (as in,
unhygienic by default), I immediately headdesk. Hygiene is a _very_ desirable
property of a macro system, and there's really no reason _not_ to provide it.
A simple hygienic macro system (like syntactic closures, for example) is only
_slightly_ more complicated to implement than an unhygienic one, and you're
doing your target audience a huge favor. No, gensym is not enough.

I'm also bewildered by arguments that define-macro (Common Lisp-style macros)
is easier than syntax-rules. The builtin pattern-matching offered by syntax-
rules makes writing macros _so much easier_. Yes, template macros are less
powerful than procedural macros, but in my experience they cover upwards of
90% of macros you might want to write, and they do away with tons of
boilerplate code. For the other 10%, most Scheme systems _do_ offer procedural
macros in addition. I was a little disappointed that R7RS left syntax-case out
because it was nice to finally have a standard facility for defining low-level
and procedural macros. Perhaps there's hope yet for R7RS-large.

[0]: [http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-
Z-H-13.html...](http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-
Z-H-13.html#node_sec_12.6)

[1]: [http://wiki.call-cc.org/man/4/Macros#ir-macro-
transformer](http://wiki.call-cc.org/man/4/Macros#ir-macro-transformer)

------
doomrobo
A great tutorial on macros in Racket is Fear of Macros by Greg Hendershott.

[http://www.greghendershott.com/fear-of-
macros/all.html](http://www.greghendershott.com/fear-of-macros/all.html)

------
lispm
In Lisp the macro for a symbol, is called a symbol macro. Symbol macros are
defined with DEFINE-SYMBOL-MACRO and SYMBOL-MACROLET.

Functions defined in a file where a macro is defined and used are also not
seen by the file compiler. The use of (EVAL-WHEN (:COMPILE-TOPLEVEL) ...)
instructs the file compiler to execute the enclosed forms at compile-time.
Thus one can tell the compiler to define a function in the compile time
environment or to load some code at compile time via LOAD or REQUIRE.

~~~
felideon
Interesting. I haven't written much Lisp but I was under the impression DEFUN
had some black magic that obviated the need for that.

~~~
white-flame
Common Lisp has a number of various namespaces that symbols can be defined in.
Interfaces like DEFUN, DEFMACRO, and DEFINE-SYMBOL-MACRO all are explicit
within their domain, and have explicit rules as to what is available when with
respect to read/compile/evaluate times.

Common Lisp is a very explicit language, and that explicitness allows macros
to easily coexist with other code, because nothing is hidden by syntax or
"black magic". (By default. Because you can morph the language to do nearly
anything you'd want to.)

------
js8
I don't know macros in Racket, only Common Lisp, but I am curious what are
some good uses of non-reader macros that cannot be well expressed in normal
Haskell?

~~~
xrange
Anywhere you see a "deriving" clause is a place where macros would have
probably have sufficed instead of requiring support from the compiler.

~~~
coolsunglasses
The deriving (and related generics) functionality is quite a bit cleaner and
more appropriate to the problem than macros.

~~~
marcosdumay
Except when you want to derive something that isn't supported by the compiler,
and isn't creating a newtype.

~~~
coolsunglasses
So you haven't used Haskell? I can use `Generic` or TH to derive anything for
anything I want. In the case of the latter, it doesn't even have to be a
typeclass instance.

------
qwertyuiop924
Should I mention now that I think syntax-case is an awkward hacky mess, that
syntactic closures solved the problem better, as did er and ir transformers
from chicken? (Although ir-macro-transformer is O(n), because it crawls your
macro so that sucks)

Should I also mention that I feel that this is endemic of Racket itself, which
tries to be the be-all-end-all by taking everything from everyone, but winds
up being an awkward mess, as it tries to integrate DBC with FP and a (fairly
weak, iirc) OO system, not to mention custodians, and a ton of other stuff
that should have been spun off into modules and libraries, but is cluttering
up core and makes me long for chicken, and want to hurl whenever I look at it?

Don't get me wrong, a big stdlib is always handy, but for god's sake, pick a
SMALL set of core concepts and stick with them.

Now if you'll excuse me, I'll go get some kevlar. Knowing you lot, I may need
it after this.

~~~
samth
Note that the contract system and the OO system are built entirely with
libraries, rather than being in the core. Also, syntax-case is implemented
with a library on top of a much simpler core. We Racketeers spend a lot of
time working on how to simplify and shrink the core -- Matthew Flatt's recent
work on hygiene via sets of scopes is an example of exactly that
simplification.

Custodians are in the core, but it's not possible to move them to a library --
they necessarily have to integrate with things like the garbage collector. You
couldn't have the things they provide, like the ability to limit part of your
program to a specified amount of memory, without that.

~~~
qwertyuiop924
What I meant was that the contracts and OO are integrated strongly into
racket's stdlib: instead of providing a basic paradigm, and then providing
alternatives, like most schemes do, Racket's default paradigm is, "all of
them." That creates an overcomplicated mess.

~~~
samth
Right, we think that contracts and OO programming are both important things
that programmers want and need, and so they deserve high quality support from
the language. The traditional Scheme approach of not making choices and not
integrating things hasn't served anyone well, and isn't the approach that
other languages like Python or Ruby take.

~~~
qwertyuiop924
But Racket isn't making choices either. Python and Ruby say that procedural
programming is the choice for small projects, and OO is what you should be
using for larger projects. You can do other things, but that's the default.
Racket's approach is to toss 50-odd paradigms at you, and say: "screw it, you
figure it out." At least with the traditional scheme there was a default: a
sort of procedural paradigm, with some functionalism mixed in. With Racket,
there isn't even that. Give us contracts and OO, and all the other paradigms,
but give us a reasonable default paradigm as well.

~~~
samth
Racket provides contracts for all paradigms you might want to use -- DBC is
not a separate paradigm.

The reasonable default is writing programs with structures and functions. Most
people use OO primarily for the GUI (where OO works quite nicely). This is the
same paradigm that you'd find in ML or Rust or Go in many cases.

~~~
qwertyuiop924
_shrugs_

The docs could have fooled me. You try to learn anything about Racket, and
pretty soon, you're 10 levels deep into nonsense about contracts and other
junk that you shouldn't need to see just to find the semantics of cons, or
define. Which is to say, the docs are bad. But that's excusable.

What I do NOT find exusable, and what started this conversation, is that
syntax-case is overcomplicated compared to, say, er and ir macros, or
syntactic closures, all of which were already available. So use a more complex
system, when it's been shown a simpler one will do?

~~~
samth
For posterity, here are the docs for `cons`: [http://docs.racket-
lang.org/reference/pairs.html?q=cons#%28d...](http://docs.racket-
lang.org/reference/pairs.html?q=cons#%28def._%28%28quote._~23~25kernel%29._cons%29%29)

I don't see 10 levels of nonsense there. But clearly you're not happy about
larger aspects of the system that it's probably not worth debating here. But
let's just say that (a) syntax-case is a pattern matching library, not a macro
system, and (b) Racket's macro system has demonstrated its usefulness by
building those libraries that you claimed were built in, such as OO systems
and contracts.

~~~
qwertyuiop924
Racket's macro system has proved it is as capable as any other macro system.
No more, no less. I merely argue that it is overly complicated.

------
peterbotond
Racket is a programming language programming language to program and prove
programming languages. See eopl. (edit: yes it is a pun and a brain twister. i
love racket (and scheme too))

