Alex M.
Alex M.

Reputation: 503

Testing for the definedness of a variable in Scheme

Besides regular definitions like (define variable value) Scheme also allows for uninitialized definitions like (define variable), when the variable is bound to some value later on with the aid of set!.

Are there procedures allowing to test whether a symbol is a defined (not necessarily initialized) variable? Something like (defined? 'variable) which should return #t if variable is defined and #f otherwise?

Upvotes: 2

Views: 777

Answers (3)

Anton Baukin
Anton Baukin

Reputation: 61

There is simple universal solution based on try-catch. I've tested it on Gambit v4.9.3.

(define (defined? var-symbol)
 (with-exception-handler
  (lambda (e) void)
  (lambda () (not (eq? void (eval var-symbol))))
 )
)

And here are the test cases:

> (defined? 'a)
#f
> (define a 123)
> (defined? 'a) 
#t
> (defined? 'x)
#f
> (define x)
> (defined? 'x)
#t
> (defined? 'f)
#f
> (define (f a b) (+ a b))
> (defined? 'f)           
#t

With this you can make conditional definitions:

(define (define-if-not var-symbol init)
 (if (defined? var-symbol) void
  (let ((value (init)))
   (eval (list 'define var-symbol value))
   value
  )
 )
)

The tests:

> (define a 'abc)
> (define (make-123) 123)
> (define-if-not 'a make-123)
#<procedure #2 void>
> (define-if-not 'b make-123)
123
> a
abc
> b
123

This variant of define-if-not has a caveat: if the value is a symbol, eval treats it as a reference. This is the short demo:

> (define value 123)
> (eval (list 'define 'a value))
> (define value 'abc)           
> (eval (list 'define 'a value))
*** ERROR -- Unbound variable: abc

The following [nasty] solution overcomes it for Gambit:

(define define-if-not-private)

(define (define-if-not var-symbol init)
 (if (defined? var-symbol) void
  (let ((value (init)))
   (set! define-if-not-private (lambda () value))
   (eval (list 'define var-symbol '(define-if-not-private)))
   value
  )
 )
)

Upvotes: 2

Sylwester
Sylwester

Reputation: 48765

According to the scheme report (standard) define is used to define a variable. (put it into existence) Using define without an expression like (define test) defines the variable to some value chosen by the implementation.

It can only be used once in the same scope for the same variable, thus set! is to alter an existing variable to something else, but it cannot remove it's existence.

There is nothing in the report to remove a binding or to check if a binding exists. Using a variable without it being defined will have undefined consquences.

Implementations may have feature beyond the report. These things are typically stuff the guts of a scheme system should know about and they may expose it to the user, but it is implementation dependent and not standard.

Upvotes: 1

soegaard
soegaard

Reputation: 31145

The form (define var) is a non-standard extension.

According to R5RS define has one of the following forms:

(define <variable> <expression>)
(define (<variable> <formals>) <body>)
(define (<variable> . <formal>) <body>)

As far as I can tell the same goes for R6RS and R7RS.

Most likely (define foo) expands to (define foo the-undefined-value) in your implementation. This means you can use (if (eq? foo the-undefined-value) ...) to test whetherfoo` is has been initialized or not.

http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_idx_190

Update:

I checked R6RS and it says:

(define <variable> <unspecified>)
where <unspecified> is a side-effect-free expression returning 
an unspecified value.

Consider

(define foo)
(define bar)

It is up to the implementor whether the same unspecified value that is bound to both foo and bar.

Try this program:

(define unspecified)
(define unspecified1)
(eq? unspecified unspecified1)

If the program evaluates to #t then you can write an initialized? predicate like this:

(define unspecified)
(define (initialized? x) (not (eq? x unspecified)))

(define test)
(initialized? test)
(set! test 42)
(initialized? test)

Upvotes: 2

Related Questions