tags:

views:

72

answers:

3

I am trying something out in Scheme for fun. I'm trying to make an Area function that gets the type of the thing it's operating on and then calls a different function depending on the type of object. Here's my code:

(define (area object)
  (if (not (null? (eval (word 'area- (get-type object)))))
      (eval (list (word 'area- (get-type object)) 'object))
      #f
  )
)

Scheme doesn't like this because it says object is an unbound variable. No, I can't take the quote away because then it's actually placing the value there and then Scheme complains that the list is malformed.

What can I do to use the value in object within eval?

Note: Scheme apparently grabs the global variable "object" just fine, so it's basically ignoring that it's inside a function.

Some information for a related language is here: http://docs.racket-lang.org/guide/eval.html, which seems to indicate that there isn't a solution in Scheme, but if you know of one I'd like to hear it.

+2  A: 

There isn't one -- and that's a feature. eval is doing an evaluation of a form that was generated dynamically at runtime. So, if it needs to know about local bindings, then you'd need to compile (lamba (x) x) and (lambda (y) y) differently -- because the name matter. But this is just the tip, there's a whole bunch of issues around implementing this kind of feature.

As for your problem -- even if it was possible to do what you want to, it's a fragile solution that depends on name. Remember that in Scheme you can use functions like any other value -- so instead of calling get-type and combining it with some symbol to get a name, make your objects contain the function that is needed (which at that point would be better called "method").

Something like:

(define (area object)
  ((get-area-method object) object))

Obviously doing this means that there's little point in not going the whole way with:

(define (area object)
  (get-area object)

which is just

(define area get-area)

But the first might be more typical of a general OO-like system, with a way to get methods, so it might be useful for you. That direction could take you to:

(define (area object)
  ((get-method object 'get-area) object))
Eli Barzilay
A: 

Apparently there IS a way to do what I wanted to do in Scheme. Here is the code:

(define (area object)
  ((eval (list 'identity (word 'area- (get-type object)))) object)
)

Basically the trick is this: Because eval only knows about global variables, I can still use the identity function within eval to return the value of a global variable. The global variable I'm interested in in this case is a function, which is just like any other variable. I can then return this value and use it as the procedure to call on my original argument. This lets me construct the name of the variable I want to get from global scope and get and use the procedure contained in that variable, accomplishing the result I was looking for.

Here's a cleaner version:

(define (area object)
  ((get-function (word 'area- (get-type object))) object)
)

(define (get-function function)
  (eval (list 'identity function)))

Apparently the is-not-null part will not work, however, because trying to get the identity of a function that doesn't exist causes an unbound variable error. So, still need to be careful to call the operator on a type that supports it.

Doug Treadwell
This is very wrong. Start from the fact that using `'identity` is redundant (you can just `eval` the symbol directly -- same result), and from there you get to your "clean" version of `get-function` which is itself just an identity function.
Eli Barzilay
But how do I eval a constructed symbol? Maybe (eval (list (append something? Problem with that is, eval constructs a procedure from the list, so if I (eval 'thing) it really calls (thing) with no arguments, rather than just returning the value of thing.
Doug Treadwell
No, that's wrong. `(eval 'x)` just returns the value of the global `x`, it doesn't call any function. (But see my answer -- you shouldn't use `eval` for such things. It's guaranteed to be a bad solution.)
Eli Barzilay
Good catch, I thought eval was evaluating it, but that was because I was passing it inside a list which makes eval call it. Thanks for that. You may want to submit a solution that modifies my version and I'll select it.
Doug Treadwell
I don't know what solution you're expecting -- the whole concept of using `eval` the way you want to use it is (still) fundamentally broken.
Eli Barzilay
Why is that, Eli?
Doug Treadwell
Answering that, in a way that is more in-depth than my answer above, is not something that I can do in a comment... But it's also not needed: just google around and you'll find a ton of discussions on the subject. In any case, the bottom line is that for your needs (an OO-like system) just using function values would work out much better.
Eli Barzilay
+1  A: 

Racket has classes and methods, and you should use it!

(define circle%
  (class object%
    (init radius)
    (define r radius)
    (super-new)
    (define/public (area)
      (* pi r r))))

(define rectangle%
  (class object%
    (init width height)
    (define w width)
    (define h height)
    (super-new)
    (define/public (area)
      (* w h))))

(define unit-circle (new circle% [radius 1]))
(define unit-square (new rectangle% [width 1] [height 1]))

(send unit-circle area)   ; => 3.141592653589793
(send unit-square area)   ; => 1

Much less hacky than name-based dispatch.

Chris Jester-Young