Meta-note: I'm possibly being over-optimistic here, but I'll assume
that you are actually interested in hearing explanations etc. (If
not, please indicate so by redundant sarcasm and/or cheap attempts at
offensiveness.)
Eddy Sputz <
eddie...@gmail.com> writes:
>
> Note: some of what I discuss is specific to Racket but it just
> illustrates the attitude among Scheme people and programming
> language "theorists" in general.
At least in the Racket case, you should keep in mind that the macro
system (as well as other related parts like modules etc) are being
driven by practical needs first, not by obscure theories.
> The [back-]tick mark is one of Lisp/Scheme's greatest features.
[Unrelated side-note: this feature exist in many languages now, often
with strings, sometimes in a more sexpr-like way.]
> (Note to self: it would be nice if there was a string version of
> backquote, comma, at-comma etc.)
http://barzilay.org/misc/scribble-reader.pdf
> [...] Okay how about Scheme. Now Scheme really, really wants you to
> use "hygenic" macros.
You're mixing 2.5 independent concepts: hygiene and phase-separation,
and my guess is that you do the usual conflation of hygiene with the
simple `syntax-rules' kind of rewrite-rule based macros. WRT the
latter point, you can use `define-syntax' to define simple functions
as you would in CL, modulo the fact that we use a different data type
instead of plain sexprs:
http://blog.racket-lang.org/2011/04/writing-syntax-case-macros.html
The `define-macro' compatibility hack is, roughly speaking, taking the
input syntax and converts it into sexprs, applies the transformation
function, then uses a hash-table lookup that was constructed in the
first step to guess how to convert the resulting sexpr back into a
syntax value. It's obviously not a perfect process since downgrading
the data to sexprs is a lossy process.
> Okay let's try it:
>
> (require mzlib/defmacro)
>
> (define (blah x)
> (+ x 1))
>
> (define-macro (foo . xs)
> `(printf "~a\n" ,(blah (car xs))))
Here you're getting to the phase separation feature. The quick-quick
version is that code that lives in the syntax world is unrelated to
code that lives in the runtime world. There's a bunch of ways to
specify that code goes into the syntax world, two popular ones:
1. Wrap the code in a `begin-for-syntax':
(begin-for-syntax (define (blah x) (+ x 1)))
and your code now works.
2. Another option when you have a whole bunch of code that lives in
its own module is to use (require (for-syntax "blah.rkt")).
In the first case you obviously get no `blah' bound in the runtime
code. In the second, you can do that by requiring the same code in
both levels *but* the two levels are still unrelated. For example:
-> (module blah racket
(define c 0)
(define (blah) (set! c (add1 c)) c)
(provide blah))
-> (require mzlib/defmacro 'blah (for-syntax 'blah))
-> (list (blah) (blah) (blah) (blah) (blah))
'(1 2 3 4 5)
-> (define-macro (foo) (blah))
-> (list (foo) (foo) (foo))
'(1 2 3)
-> (list (blah) (blah) (blah) (blah) (blah))
'(6 7 8 9 10)
Now, before you get all excited about the wonderful feature you have
to give up, it is useful to read this:
http://fare.livejournal.com/146698.html
and realize that phase separation is directly resolving such issues.
Side note: as I said above, this has nothing to do with hygiene.
Side note #2: if you read the above and still think that you can't
live without this feature then CL is the right choice for you.
> "bindings in phase level 1 constitute the transformer
> environment. [...]"
>
> Okay whatever. Look guys, speaking in your own dense,
> jargon-infested verbiage
Yes, you're looking at a *reference* page labeled "Syntax Model"... so
you should expect some dense verbiage. If you want a more readable
description, then have a look at the guide instead, specifically:
http://docs.racket-lang.org/guide/stx-phases.html
> doesn't make what you're saying any more correct than something some
> other idiot has to say.
(Correctness doesn't enter into it. It is a description of what
Racket is doing.)
> "Tamper status" are they serious? Jesus nobody can be that paranoid
> can they? I mean, these are friggin' macros we're not talking about
> not the Mona Lisa or some piece of equipment vital to national
> security.
This feature refers to maintaining scope and visibility of bindings,
and it is definitely not something that newbies should deal with. If
you really want to know, the idea is roughly like this: when you
define a module, you can choose which identifiers are provided and
which are not. (Note that from a CL package point of view, this is
already a bad idea so if you don't want to allow people to have
private bindings you'd better stop reading this.) The thing is that
you might have a macro that expands into such a private binding. For
example:
(module foo racket
(define c 0)
(define (c!) (set! c (add1 c)) c)
(define-syntax-rule (counted-eval expr)
(begin (printf "#~a ~s\n" (c!) 'expr) expr))
(provide counted-eval))
The author of this module wants to have each use of the macro get
counter properly, without other changes to the internal data --
otherwise more bindings would be provided. But still, since the macro
is available, you can use `expand' manually, get some resulting code
that has the internal identifier and pull it out (no, that's not how
you'd pull something out in practice unless you're a dentist, I'm just
trying to keep it clear):
-> (counted-eval (+ 1 2))
#1 (+ 1 2)
3
-> (cadddr (syntax->list (cadr (syntax->list (expand #'(counted-eval (+ 1 2)))))))
#<syntax:1:132 (#%app c!)>
then use it directly:
-> (eval (cadddr (syntax->list (cadr (syntax->list (expand #'(counted-eval (+ 1 2))))))))
; #%app: cannot use identifier tainted by macro transformation
So this is where those "taints" come into the game, and prevent you
from doing that kind of thing.
(Again, this is *not* something that CL can do or tries to do or
considers a good thing.)
> Holy shit. These guys take their macros seriously.
Yes -- and the above example is clearly not showing any great need for
such seriousness. But bear in mind that large parts of Racket are
written in Racket, and that core code relies on such protection to
hide some dangerous implementation details which could otherwise break
internal variants that can lead to segfaults.
And some obvious side-notes:
1. To repeat this yet again, feel free to scream bloody murder on such
a horrendous feature. If you want that, then how about I make that
easy and give you a few choices that so you can just choose one:
a. If you do these kind of tricks you deserve what you get.
b. Programmers should be allowed to shoot their own feet.
c. ... thieves and bums use C++ and nice people use CLOS.
2. Yes, we have an FFI, so you can very easily find other, more
straightforward ways to segfault. But OTOH, there is also a way to
drop the trust level when you load some untrusted code.
3. You started with the "theorists", but note that this is a very
practical issue. I run a homework submission server that evaluates
submission code, and I know that students can hack my machine
through it. There are many other cases where sandboxing is a very
desired feature, and AFAICT, this kind of sandboxing isn't possible
in CL without something external like running a separate restricted
process.
--
((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay:
http://barzilay.org/ Maze is Life!