15

In the context of writing Racket macros, what does "3D syntax" mean?

I've heard the phrase a few times. Including once in reference to a macro I was writing. But that was awhile ago; I fixed it, and now I can't remember exactly what I was doing wrong originally.

Also: Is 3D syntax always bad? Or is it like eval (where if you think you need to use it, you're probably wrong, but there are some valid uses in expert hands)?

Greg Hendershott
  • 16,100
  • 6
  • 36
  • 53
  • In the future, maybe everything will be well documented! +1 for the difficulty. Looks like a good bounty question to me. The only thing I could relevantly locate was this discussion: http://lists.racket-lang.org/dev/archive/2013-January/011637.html – jdero Jul 02 '13 at 22:59

1 Answers1

11

Syntax objects are usually supposed to be just serializable data. 3D-syntax weakens this condition: it allows us to sneak in arbitrary values, and not just plain data. That's what makes them "3d": they are values that rise above the regular flat things you'd expect out of syntax objects.

For example, we can sneak in lambda values!

#lang racket

(define ns (make-base-namespace))
(define (set-next! n)
  (parameterize ([current-namespace ns])
    (eval #`(define next #,n))))    ;; <--  3d-syntax here

(define (compute s)
  (parameterize ([current-namespace ns])
    (eval s)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define counter 0)
(set-next! (lambda ()
             (set! counter (add1 counter))
             counter))

(compute '(+ (next)
             (next)
             (next)
             (next)))

Doing this is usually a bad thing, because the presence of such values probably means an ill-founded attempt to leak information across phases of compilation. The result is something that's likely not separately-compilable. If you see an error that sounds something like:

write: cannot marshal value that is embedded in compiled code value

then that is most likely due to a macro having produced a piece of 3d-syntax that can't be serialized to bytecode.

Sometimes, in rare situations, we really do want 3d-syntax, often in dynamic evaluation contexts. As a concrete example, a debugger in DrRacket may want to annotate the syntax of a program so that function applications directly call back into functions of the debugger, so that we can do things like interactive code coverage coloring in the program editor. In that sense, 3d-syntax can act as a communication channel between dynamically-evaluated code and its ambient environment.

dyoo
  • 11,795
  • 1
  • 34
  • 44
  • even in contexts like that, though, it seems cleaner to inject the function as a module-level dependency, somehow. Though, um, the stepper is rife with 3d syntax. – John Clements Jul 03 '13 at 00:09
  • 1
    Agreed. But I ran into some weird problems trying to do so: http://lists.racket-lang.org/dev/archive/2013-April/012126.html, and unfortunately I don't have time these days to figure out what exactly I'm missing. – dyoo Jul 03 '13 at 00:29
  • 3
    First, thank you for answering! I've been re-reading, thinking, and trying to get a handle on your answer. For one thing, I figured the answer would entail talking about syntax transformers, but you're using eval and namespaces, and I'm trying to think through what that means. Also, I'm confused and/or distracted by the use of namespaces in your example, and trying to understand if that's essential and if so how/why. [This](https://gist.github.com/greghendershott/5923364) is simpler. It gives the same answer: 10. Is it equivalent, or not? – Greg Hendershott Jul 03 '13 at 22:28
  • 1
    p.s. I hope it's clear I'm not nit-picking your answer! I just don't truly "get" it, yet. I suspect there's something really obvious and simple to you (and John), but I don't know and/or I'm failing to make some connection. – Greg Hendershott Jul 03 '13 at 22:40
  • 3
    Whenever I'm using `eval`, I pretend that everything is a "free variable" as far as `eval` is concerned, and I use a namespace to know exactly how those free variables are being resolved. I try to never use `eval` without explicitly saying or knowing what the namespace should be, because otherwise I'm trusting my caller to set up those bindings for me. Since I normally won't have control over who is calling me, I want to resolve any ambiguity, and that's why the `current-namespace` stuff is there. – dyoo Jul 03 '13 at 22:52