D.S.
D.S.

Reputation: 41

Racket - Having trouble with variables in macro

I'm currently trying to assign a variable to macro to store something:

(begin-for-syntax
  (define a 0))
(define-syntax (foo stx)
  (set! a (+ a 1))
  (datum->syntax stx a))
(foo)
(foo)
(foo)

After compiling this piece of code, the repl showed "1 2 3". However, when I entered "(foo)" in the repl, the next number was "1" rather than "4" which was I expected.

1
2
3
> (foo)
1

It looked like the variable "a" was reset after the compiling had done. The same thing happened when I "require" this code in another module.

Is it possible to solve this problem?

Upvotes: 4

Views: 166

Answers (2)

Alex Knauth
Alex Knauth

Reputation: 8373

Racket has the separate compilation guarantee, which means that compilation of separate modules behaves consistently, because mutation and effects are reset just as if they were compiled separately, even when compiled together. Here the REPL behaves the same way.

One way to get around that for this particular situation is to have the macro expand to a begin-for-syntax that does the mutation instead of trying to do it in the macro transformer:

#lang racket
(begin-for-syntax
  (define a 0))
(define-syntax (get-a stx)
  (datum->syntax stx a))
(define-syntax (foo stx)
  #'(begin
      (begin-for-syntax
        (set! a (+ a 1)))
      (get-a)))
(foo)
(foo)
(foo)

Entering (foo) in REPL for this returns 4.

But for other modules, this is disallowed because set! cannot mutate a variable from a separate module.

Upvotes: 1

uselpa
uselpa

Reputation: 18917

I can't really explain why it doesn't work, but I feel that "hiding" a variable in phase level 1 might not exactly be the right approach. You can pretty much achieve the same with modules and without macros:

(module adder racket/base
  (provide foo)
  (define a 0)
  (define (foo)
    (set! a (add1 a))
    a))

(require 'adder)
(foo)
(foo)
(foo)

prints

1
2
3

but then the sequence goes on at the REPL level:

> (foo)
4
> (foo)
5

Of course you can also use a simple closure:

(define foo
  (let ((a 1))
    (lambda ()
      (begin0
        a
        (set! a (add1 a))))))

Upvotes: 2

Related Questions