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.