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 func开发者_高级运维tion.
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.
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))
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.
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.
精彩评论