2
votes

This question is similar in scope to: In R6RS Scheme, is there a way to get the current environment for use with eval? but I'd like to take it a step further and ask how you'd remedy something like this.

My issue is further confounded in that in my case '(+ x y) is an arbitrary unevaluated lambda statement. Unevaluated because it may contain calls to variables that are part of the let (and since Scheme doesn't have faith that the procedure will be called in an environment containing those variables when the current one does not, it fires off an undefined identifier error). So the question becomes: how can I restructure my code, such that this scoping nightmare is no longer an issue? I'd like to still be able to use the environment variables from the let whenever the lambda is called.

I'm using Pretty Big

The intent is to create classes in Scheme. My approach so far is pretty big (no pun intended) but looks like:

    (define (dispatch msg methods args)
      (if (null? methods) (display "Method signature not found.")
          (let (
                (m-name (caar methods))
                (m-args (cadar methods))
                (m-body (caddar methods)))
            (if (and (eq? msg (caar methods)) (eq? (length args) (length (cadar methods))))
                `(lambda ,m-args ,m-body)
                (dispatch msg (cdr methods) args)))))

    (define (build-lets c-def)
      (let (
            (i-vars (cadr c-def))
            (meths (caddr c-def)))
        (eval `(append ',i-vars (list (list 'methods '',meths))))))

    (define (new c-def . args)
      (apply (eval `(lambda ,(map cadr (cadr c-def))
               (let* ,(build-lets c-def)
                 (lambda (msg . args)
                   (letrec ((meth (dispatch msg methods args)))
                     (apply meth args))))))
             args))

Where c-def is a class def of the form (say for a point)

    '(();Name of parent
      ((yvalue y) (xvalue x)) ;Instance variables: (i-var constructor-arg)
      ((getx () xvalue) ;Methods, ((name args body) ...)
       (setx (x) (set! xvalue x)))))
2
I'm not sure what you're trying to do, do you have some example code?Chris Jester-Young
@ChrisJester-Young I think he wants to figure out a way so that something like (let ((x 1)) (eval '(* x 2))) will return 2.Alex V
@Maxwell that's exactly rightJess The Witch
@Maxwell That specific case can be handled with Guile's local-eval, but that's not anything that Racket supports. So I'll look at the OP's updated question and see if there's a different way to go about it.Chris Jester-Young
@KPatnode For your specific use case, I think you're going to have to use macros (and not eval). Certainly, Racket's built-in classes stuff is all done using macros.Chris Jester-Young

2 Answers

3
votes

This doesn't implement all the syntax you have in mind, but it might illustrate the techniques needed to implement it cleanly.

(define-syntax make-object
  (syntax-rules ()
    [(__ ([ivar ival] ...) ([method-name args body ...] ...))
      (let ([ivar ival] ...)
        (λ (msg . oargs)
          (cond
            [(eq? 'method-name msg)
              (apply (λ args body ...) oargs)] ...
            [else
              (error 'object-system "unknown message" msg)])))]))

(define o (make-object ([xvalue 'x])
                       ([getx () xvalue]
                        [setx (x) (set! xvalue x)])))

(o 'getx) => x
(o 'setx 'blah)
(o 'getx) => blah

The trick is to write a macro that makes a closure. The instance variables go in a lexical scope (the let) that holds the closure. The closure is the dispatcher. The methods are lambdas defined within the dispatcher, so the instance variables are within the same scope as the methods.

0
votes

If you can make let part of the eval then it will work. Below is an example:

Something similar (but simpler) to your code which doesn't work:

(define (hello m)
  (let ((msg m))
    (eval '(print msg))))

Making let part of eval make it works:

(define (hello m)
  (eval `(let ((msg ,m))
           (print msg))))