I have a question related to variable scoping during macro expansion. I posted something briefly earlier today but noticed that the question was not well stated and deleted it. If anyone would be willing to take a look, I would appreciate it. I’m putting some code to illustrate the question in a thread on this comment.
Essentially I am trying to rebind a variable in a loop to make a loop iterator. The code that exposes the issue looks like this:
(define-syntax-rule (loop id-sequence term-clause statement)
(λ (input-list) (for/fold ([l input-list])
([index (range 0
(length input-list)
(length (cdr 'id-sequence)))])
(begin
(define op 1)
(statement l)))))
Where statement
is meant to have scope on op
.
Here is an example statement: (statement (pointer-assignment op 4))
It seems clear that the reason this is not working is because of macro hygiene. I feel like the usual answer related to getting around these types of issues involves using syntax parameters, but I can’t seem to get that to work without defining the parameter in a hard coded way at the module level, but I do not know the identifier name until macro expansion time so that does not seem like it will work.
For anyone happening upon this, I think I have figured out that the answer is going to be unpacking the statement
and repacking it into syntax with the required identifiers in scope. I have not implemented this yet but I think this is one of the use cases where tightly controlling the context makes sense. Implementation is hanging me up a bit so if anyone wants to lend a hand there, let me know!
As a small working example, I’ve proven out at least this much:
(define-syntax-rule (program subprogram)
subprogram)
(define-syntax (statement stx)
#`(let ([op 3]) #,(car (cdr (syntax->datum stx)))))
(program (statement (+ 1 op)))
> 4
The hard part will be cleanly doing it inside of a for/fold and making sure that it is not too eager (as the index is a pointer into the accumulator which can change inside the loop)
Can you give a full example how you expect your macro to be used?
Certainly! The full file (with example input syntax at the bottom) looks like:
(provide #%module-begin)
(require threading)
(require "../util.rkt")
(provide read)
(provide delimiter)
(require lens)
(define-syntax-rule (tape-program read statement ...)
(for/fold ([l read])
([transform (list statement ...)])
(transform l)))
(provide tape-program)
(define-syntax-rule (statement substatement) substatement)
(provide statement)
(define (pointer-assignment target-pos new-val)
(λ (l) (lens-set (list-ref-lens target-pos) l new-val)))
(provide pointer-assignment)
(define-syntax-rule (loop id-sequence term-clause statement)
(λ (input-list) (for/fold ([l input-list])
([index (range 0
(length input-list)
(length (cdr 'id-sequence)))])
(begin
(define op 1)
(statement l)))))
(provide loop)
(tape-program
(read 2 (delimiter "comma"))
(statement (pointer-assignment 1 12))
(statement (pointer-assignment 2 2))
(statement
(loop
(identifier-sequence (identifier op) (identifier foo) (identifier bar))
(termination-clause op "=" 99)
(statement (pointer-assignment op 4)))))
The idea is that each statement writes back to a tape. Loops are statements that run substatements over and over again during tape traversal. The tape may change during this traversal.
So I think the solution will be to unpack the identifier sequence and bind each one to an offset of the iterator inside of the for/fold
. Either way, I think this will be something to sort of chug on for awhile.
Is there a way to have rackunit
render pict
s inside the “check-info stack”?
what’s the point of having separate let
and letrec
? Why not have just letrec
since it can do everything let
can (and more)?
If you shadow a variable using let, changing that to letrec won’t work.
Also, using less powerful constructs can make things easier to understand
@samth this seems to work, I don’t understand what you mean "letrec.rkt"> (letrec ([x 5]) (letrec ([x 6]) x))
6