I started this as some quick comments, but it wrote itself into a
long-winded explanation of why what Racket does is following the Lisp
tradition *more* closely than other Lisps. Feel free to skip if
you're not into meta-meta-syntax discussions...
Two hours ago, Danny Yoo wrote:
>> So there's been a lot of thought about this, and the default in
> Racket skews toward making it easier for students to think about
> calculating.
I disagree with the "for students" part. It's specifically trying to
make it easier to use values printed on the *repl*, which is not where
(htdp-style) newbies should spend much time. So IMO it's something
that is useful in any context.
An hour and a half ago, Danny Yoo wrote:
> > Yes, I got this to work. But, what if I don't want to use
> > DrRacket, but just pure old console Racket.exe instead? Or emacs?
>> [...]
>> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> > (print-as-expression #f)
> > (list 1 2 3)
> (1 2 3)
Even easier -- just use (current-print write), and you get the
"traditional" behavior. Here's a quick demo:
-> (current-print)
#<procedure:pretty-printer>
-> '(current-print write)
'(current-print write)
-> (current-print write)
#<void>
-> '(current-print write)
(current-print write)
Note that it also demonstrates how confusing things can be. IME,
explanations on what Lisp's "'" is doing are extremely frequent.
An hour and a half ago, Stephen Bloch wrote:
>> Actually, in many ways I sympathize: when I first encountered Racket
> (which was then called PLT Scheme), I took a similar
> interpreter-based approach to understanding what it was doing
> (having previously encountered Lisp and learned the same
> "expressions are lists" dictum you learned). It turns out not as
> helpful as one would think.
The general principle is fine: for a language that allows
meta-facilities like macros and `eval', you obviously need access to
the structures that the implementation uses to represent code. This
*has* always been one of Lisp's core ideas. But the *implementation*
of this idea -- using *just* plain S-expressions for representing code
falls short in many ways. For example, you cannot represent source
location which is extremely important when you have a syntax error in
some big source file.
Now, in the Lisp world (mostly in CL), there is a tendency to stick
with the code == sexprs equivalence no matter what. As a result,
there are various hacks around these problems. The main one is to use
a hash table that can fake adding the extra information to the sexprs,
by mapping them into the extra information. That doesn't work with
symbols: they're interned, so you cannot distinguish two occurences of
the same symbol, and in some of these systems you can observe that
limitation of the system (for example, they won't have source
information for these cases). Also, writing well-behaved macros can
be a PITA, because such macros need to be aware of this implicit extra
information and make sure that they produce sexprs with the correct
information artifically attached in the same way.
To make things worse, in Scheme you have the issue of hygiene, which
-- again -- means that you need more than plain s-expressions to
represent code. Here too, there are solutions like the above, and
solutions where some kinds of expressions (mostly symbols) are put in
a "wrapper" that holds the extra syntactic context information. (You
can see this in Scheme implementations where syntax is "mostly
sexprs", except for identifiers that are no longer plain symbols.)
> In fact, the Racket reader does NOT represent expressions as lists;
> it represents them as "syntax objects", which (as I understand it)
> can be nested in the same way lists can, but carry some additional
> information.
Right -- Racket's solution is to dump the illusion of direct
correlation between syntax (= syntax objects) and data (s-expressions)
and instead use a new type for syntax (which, unsurprisingly, is
similar enough to s-expressions that it is trivial to convert syntax
to sexprs).
But that's not dumping the "spirit of Lisp" -- if anything, it's
*following* that spirit to the letter. The *core* idea above is the
one of "reifying" the compiler's representation in a way that is
accessible to user code -- and that was the great thing about sexprs.
As it turned out throughout the decades, plain sexprs cannot provide
sufficient information -- so instead of following what everyone else
does and cheating the additional information in, Racket chooses to
expose the *true* syntax objects to user code. Hopefully this
explains why I view this as being a more lispish solution than the one
taken by, for example, common lisp.
An additional benefit of that is that it is easier now to accomodate
new languages. You are no longer bound by the limits of plain sexprs;
your syntax representation is something that is more conveniently
treated as what it was supposed to be: the intermediate "Abstract
Syntax Tree" representation of code. In other words, it is a direct
result of sticking to the "Code is Data", and even preferring that
over "Code is Sexprs".
Yet another coincidental advantage that is more student-oriented, is
the fact that there is a better separation between representation of
code and representation of other data. You're much less likely to
confuse one for the other. It's mostly legacy that still makes the
Racket `eval' accept sexprs. It would be cleaner to make something
like (eval '(+ 1 2)) return '(+ 1 2) like any other quoted data, and
instead require (eval #'(+ 1 2)) -- or (eval (syntax (+ 1 2))) if you
want to evaluate the (+ 1 2) expression. Even better would be to make
eval throw an error when it accept anything that is not a syntax
object to begin with. That would make the difference between 123 and
#'123 more meaningful, instead of the other Lisp-inspired hack of
equating the 123 syntax with the 123 value. (And that's BTW, one of
the very nice properties of 3-Lisp, which Matthias mentioned earlier.)
--
((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay:
http://barzilay.org/ Maze is Life!