views:

542

answers:

4

Hi guys, I've been reading through SICP (Structure and Interpration of Computer Programs) and was really excited to discover this wonderful special form: "make-environment", which they demonstrate to use in combination with eval as a way of writing modular code (excerpt from section 4.3 on "packages"):

(define scientific-library
  (make-environment
   ...
   (define (square-root x)
    ...)))

They then demonstrate how it works with

((eval 'square-root scientific-library) 4)

In their example, they then go on to demonstrate exactly the usage that I would want - an elegant, minimalist way of doing the "OO" style in scheme... They "cons" together a "type", which is actually what was returned by the "make-environment" special form (i.e. the vtable), and an arg ("the state")...

I was so excited because this is exactly what I've been looking for as a way to do polymorphic dispatch "by symbol" in Scheme without having to write lots of explicit code or macros.

i.e. I want to create an "object" that has, say, two functions, that I call in different contexts... but I don't want to refer to them by "car" and "cdr", I want to both declare and evaluate them by their symbolic names.

Anyway, when I read this I couldn't wait to get home and try it.

Imagine my disappointment then when I experienced the following in both PLT Scheme and Chez Scheme:

> (make-environment (define x 3))
Error: invalid context for definition (define x 3).
> (make-environment)
Error: variable make-environment is not bound.

What happened to "make-environment" as referenced in SICP? It all seemed so elegant, and exactly what I want, yet it doesn't seem to be supported in any modern Scheme interpreters?

What's the rationale? Is it simply that "make-environment" has a different name?

More information found later

I took at look at the online version:

http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-28.html#%_sec_4.3

I was reading was the first edition of SICP. The second edition appears to have replaced the discussion on packages with a section on non-deterministic programming and the "amp" operator.

+3  A: 

Scheme has no first-class environments because of performance reasons. When Scheme was created, it wasn't the fastest language around due to nifty stuff like first-class functions, continuations, etc. Adding first-class environments would have crippled the performance even further. So it was a trade-off made in the early Scheme days.

eljenso
+1  A: 

Would a classical dispatcher function work? I think this is similar to what you're looking for.

(define (scientific-library f)
  (define (scientific-square-root x) (some-scientific-square-root x))
  (cond ((eq? f 'square-root) scientific-square-root)
        (else (error "no such function" f))))
(define (fast-library f)
  (define (fast-square-root x) (some-fast-square-root x))
  (cond ((eq? f 'square-root) fast-square-root)
        (else (error "no such function" f))))

((scientific-library 'square-root) 23)
((fast-library 'square-root) 23)

You could even combine the example scientific and fast libraries into one big dispatch method:

(define (library l f)
  (define (scientific-library f)
    ...)
  (define (fast-library f)
    ...)
  (cond ((eq? l 'scientific) (scientific-library f))
        ((eq? l 'fast) (fast-library f))
        (else (error "no such library" l))))
(library 'fast 'square-root)
Jordan Lewis
Yes but writing the above is so tedious and verbose!
Paul Hollingsworth
+4  A: 

After more digging around I discovered this informative thread on newsnet:

"The R5RS EVAL and environment specifiers are a compromise between those who profoundly dislike first-class environments and want a restricted EVAL, and those who can not accept/understand EVAL without a second argument that is an environment."

Also, found this "work-around":

(define-syntax make-environment 
  (syntax-rules () 
    ((_ definition ...) 
     (let ((environment (scheme-report-environment 5))) 
       (eval '(begin definition 
                     ...) 
             environment) 
       environment)))) 


(define arctic 
  (make-environment 
    (define animal 'polarbaer)))

(taken from this)

However, I ended up adopting a "message passing" style kinda of like the first guy suggested - I return an alist of functions, and have a generic "send" method for invoking a particular function by name... i.e something like this

(define multiply
  (list
    (cons 'differentiate (...))
    (cons 'evaluate (lambda (args) (apply * args)))))

(define lookup
  (lambda (name dict)
    (cdr (assoc name dict))))

; Lookup the method on the object and invoke it
(define send
  (lambda (method arg args)
    ((lookup method arg) args))

((send 'evaluate multiply) args)

I've been reading further and am aware that there's all of CLOS if I really wanted to adopt a fully OO style - but I think even above is somewhat overkill.

Paul Hollingsworth
The make-environment macro is great, but I still miss one thing: I'd like to have a way to *verify* if a symbol is bound in an environment, using only standard R5RS, and as far as I know, this isn't possible without ugly hacks like storing a table of defined names, etc)
Jay
Hm, I just noticed that the make-environment trick may or may not work on a Scheme implementation: scheme-report-environment *may* be immutable, and also, the LET inside the macro may not copy the environment. There doesn't seem to be a portable way to create an environment in Scheme (at least not in R5RS).
Jay
+2  A: 

They wrote it like that because MIT Scheme does, in fact, have first-class environments, and presumably that's what the writers were planning to teach their class with (since the book was written at MIT).

Check out http://groups.csail.mit.edu/mac/projects/scheme/

However, I've noticed that MIT Scheme, while still somewhat actively developed, lacks many of the features that a really modern Scheme would have, like a foreign function interface or GUI support. You probably wouldn't want to use it for a serious software development project, at least not by itself.

Noah Lavine