Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

set! global from Scheme macro?

I am trying to write a wrapper for define, that stores the values passed to it. I've been approaching it in baby steps (being new to Lisp in general, and even newer to Scheme) but have run into a wall.

In Racket, I'm starting with:

> (require (lib "defmacro.ss"))
> (define-macro (mydefine thing definition)
      `(define ,thing ,definition))
> (mydefine a 9)
> a
9

Okay, that works. Time to do something in the macro, prior to returning the s-exprs:

> (define-macro (mydefine thing definition)
    (display "This works")
    `(define ,thing ,definition))
> (mydefine a "bob")
This works
> a
"bob"

Nice. But I can't for the life of me get it to set a global variable instead of displaying something:

> (define *myglobal* null)
> (define-macro (mydefine thing definition)
    (set! *myglobal* "This does not")
    `(define ,thing ,definition))
> (mydefine a ":-(")
set!: cannot set identifier before its definition: *myglobal*

Any suggestions on how to accomplish this would be greatly appreciated.

I suspect that I'm trying to swim against the current here, either by fiddling with globals from a macro in Scheme, or by using define-macro instead of learning the Scheme-specific syntax for macro creation.

like image 575
Duncan Bayne Avatar asked Dec 27 '22 22:12

Duncan Bayne


2 Answers

You're running against Racket's phase separation -- which means that each phase (the runtime and the compile-time) operate in different worlds. As Vijay notes, one way to solve this is to do what you want at runtime, but that will probably not be what you need in the long run. The thing is that trying these things usually means that you will want to store some syntactic information at the compile-time level. For example, say that you want to store the names of all of your defined names, to be used in a second macro that will print them all out. You would do this as follows (I'm using sane macros here, define-macro is a legacy hack that shouldn't be used for real work, you can look these things up in the guide, and then in the reference):

#lang racket
(define-for-syntax defined-names '())
(define-syntax (mydefine stx)
  (syntax-case stx ()
    [(_ name value)
     (identifier? #'name)
     (begin (set! defined-names (cons #'name defined-names))
            #'(define name value))]
    ;; provide the same syntactic sugar that `define' does
    [(_ (name . args) . body)
     #'(mydefine name (lambda args . body))]))

Note that defined-names is defined at the syntax level, which means that normal runtime code cannot refer to it. In fact, you can have it bound to a different value at the runtime level, since the two bindings are distinct. Now that that's done, you can write the macro that uses it -- even though defined-names is inaccessible at the runtime, it is a plain binding at the syntax level, so:

(define-syntax (show-definitions stx)
  (syntax-case stx ()
    [(_) (with-syntax ([(name ...) (reverse defined-names)])
           #'(begin (printf "The global values are:\n")
                    (for ([sym (in-list '(name ...))]
                          [val (in-list (list name ...))])
                      (printf "  ~s = ~s\n" sym val))))]))
like image 178
Eli Barzilay Avatar answered Jan 13 '23 22:01

Eli Barzilay


The statement (set! *myglobal* "This does not") is executed in the transformer environment, not the normal environment. So it's not able to find *myglobal. We need to get both the expressions executed in the environment where *myglobal* is defined.

Here is one solution:

(define *defined-values* null)

(define-macro (mydefine thing definition)  
  `(begin
     (set! *defined-values* (cons ,definition *defined-values*))
     (define ,thing ,`(car *defined-values*))))


> (mydefine a 10)
> (mydefine b (+ 20 30))
> a
10
> b
50
> *defined-values*
(50 10)
> (define i 10)
> (mydefine a (begin (set! i (add1 i)) i)) ;; makes sure that `definition` 
                                           ;; is not evaluated twice.
> a
11

If the Scheme implementation does not provide define-macro but has define-syntax, mydefine could be defined as:

(define-syntax mydefine
  (syntax-rules ()
    ((_ thing definition)
     (begin       
       (set! *defined-values* (cons definition *defined-values*))
       (define thing (car *defined-values*))))))
like image 39
Vijay Mathew Avatar answered Jan 13 '23 23:01

Vijay Mathew