If I understand correctly what you said the reason for (let K 3 '(print
`K)) returning (print NIL) rather than (print 3) [being K not previously
defined] is that the sequence of steps is:
Almost, there is a slight confusion in step 2. I would revise it like so:
1- read the whole let expression , here (let K 3 '(print `K))
2- reader encounters `K, thus K is evaluated (returns NIL) immediately, and
that result is substituted into the original expression before any runtime
evaluation takes place. So now the expression is (let K 3 '(print NIL)).
3- evaluate the "let prog" in the context stated by let bindings, thus
evaluates '(print NIL) in a context where K is bound to 3, but obviously K
is not used in the "let prog"
if so, would be hard to make the jump over step 2? that is making the quote
protecting the whole expression and evaluating the read-macro ` only in
step 3 when the expression is evaluated under symbol bindings stablished by
let? maybe I'm saying nosense, sorry in advance! ;-)
This issue here is readtime vs runtime. The read-macros will not be able to
access symbol bindings created at runtime. You will need to use 'fill' or
'macro' for this.
: (let K 3 (fill '(print K) 'K))
-> (print 3)
: (let @K 3 (macro '(print @K)))
-> (print 3)
This article discusses the subtleties of the process in more detail: