Make `define` evaluate its first argument in Racket

Viewed 121

In some dialects of LISP, there is a distinction between SET and SETQ, the first one evaluates its first argument so that you need to use the (SET (QUOTE …) …) syntax.

Since in Racket, quoting is not needed in definitions, define behaves as SETQ. Is there a Racket function that behaves like SET? If no, how to write one?

I tried (define (SET a b) (define (eval a) b) b) but it does not seem to work when providing it to an other language with (provide SET).

2 Answers

Here's my quick attempt at this problem:

;; lib.rkt
#lang racket/base

(provide (rename-out [@set set]
                     [@#%top #%top]
                     [@set! set!]
                     [@define define]))
(require syntax/parse/define)

(define env (make-hash))

(define (set x v stx)
  (unless (hash-has-key? env x)
    (raise-syntax-error #f "undefined id" stx))
  (hash-set! env x v))

(define-simple-macro (@#%top . x)
  (hash-ref
   env
   'x
   (λ () (raise-syntax-error #f "unbound id" (quote-syntax x)))))

(define (@set x v)
  (set x v x))

(define-simple-macro (@set! x:id v)
  (set 'x v (quote-syntax x)))

(define-simple-macro (@define x:id v)
  (begin 
    (when (hash-has-key? env 'x)
      (raise-syntax-error #f "id already defined" (quote-syntax x)))
    (hash-set! env 'x v)))
#lang racket/base

(require "lib.rkt")

(define x 1)
(set (if #t 'x 'y) 2)
(add1 x) ; 3
(set! x 3)
(add1 x) ; 4
(add1 y) ; y: unbound id in: y

Note that this differs from original Racket in several ways. For example:

  1. unbound ids are now reported at runtime instead of compile-time.
  2. set! now won't work with set!-transformer.
  3. define can't be used to define functions
  4. define can't be used to shadow an identifier.

For (2) and (3), it's possible to get the original behavior back, but I don't want the answer to be too long, so I didn't include the full functionality. For now, I don't know how to solve (4).

Also note that you can only set identifiers defined via define. If you want to set identifiers defined via lambda, let, etc., you need to redefine these constructs too.

I would do it much less verbose and much simpler.

Since all arguments are evaluated, the set or let's say define% can be defined as a function!

(define (define% x y)
    (eval `(define ,x ,y)))

One can even define functions using define% when using old-style form using lambda.

(define 'ab (lambda (x y) (+ x y)))

(ab 3 5) ;; 7

It even behaves correctly in terms of scope

(define (foo x)
    (define% 'bar (lambda (x) (+ 1 x)))
    (bar (bar (bar x))))

foo
;; #<procedure:foo>
bar
; bar: undefined;
;  cannot reference undefined identifier
; [,bt for context]

(foo 3)
6

;; after first call however, bar is available in global environment
;; as pointed out by @AlexKnauf
bar 
;; #<procedure:bar>

Thus there are some scoping issues ...

(let ((x 0))                                                                                                                           
  (define% 'counter (lambda () (set! x (+ x 1)) x)))                                                                        
counter
;; #<procedure>
(counter)
;; 1
(counter)
;; 2
(counter)
;; 3
Related