2
votes

I made my own prototypal OO system in Racket which uses "this" for methods. Everything works great, but unfortunately it prevents Racket's native class system from using "this".

Here's what I want to do:

(require (only-in racket (this oldthis)))

(define-syntax this
  (syntax-id-rules (set!)
    ((set! this ...) (void))
    ((this a ...) ((self) a ...))
    (this (if (null? (self)) oldthis (self)))))

One thing I've tried to do is rename the above "this" macro to be "newthis" and make a helper macro as follows:

(define-syntax (this stx)
  (syntax-case stx ()
    (this #'(if (null? (self)) oldthis newthis))))

But in both cases Racket throws an error because I'm using "oldthis" outside of a class context. How do I merely reference the old "this" macro without expanding it and throwing an error? Or, more broadly, how can I make my "this" macro delegate to the old "this" macro when (self) is null?

Thanks in advance!

EDIT: Here is some code that uses "this" properly:

(define Foo (new))
(void (@ Foo 'name "Foo")
      (@ Foo 'method (lambda () (@ this 'name))))

You can then run Foo's "method" by calling

((@ Foo 'method))

Which gives the correct result "Foo"

However, if I use Racket's class system:

(define foo%
  (class object%
    (super-new)
    (define/public (displayself)
      (displayln this))))

and run

(define foo (make-object foo%))
(send foo displayself)

I get (), always, since "this" returns (self) and (self) has in its closure a value "self" which is currently null. What I want to do is to check if (self) is null and, if it is, delegate to the built-in "this" macro.

EDIT2:

It looks like this answer is doing what I want to do. When I try it, however, I still get the same error about class context. I think the reason for that is that the #%top macro is invoked by (real-top . rest) and not by real-top on its own. In my case, however, oldthis gets invoked immediately and throws the error. I have no idea how to prevent that.

One thing I did that got close was to enclose oldthis in (eval 'oldthis), but that only worked in the current namespace. In order for that to work consistently, I would have to provide oldthis in conjunction with everything else, which is not only ugly but error prone, since an unsuspecting user of the library wouldn't necessarily understand that they have to pass oldthis all the time for anything to work properly. So I don't want to do it this way.

SOLVED:

(define-syntax this
  (syntax-id-rules (set!)
    ((set! this ...) (void))
    ((this a ...) ((self) a ...))
    (this (if (null? (self)) (eval 'this (module->namespace 'racket)) (self)))))

By providing the racket language as the namespace argument for eval, I can reference the old "this" without making any assumptions about the program using my OO library except that it have access to the racket language.

1
Can you give an example of " it prevents Racket's native class system from using this." ? - soegaard

1 Answers

0
votes

You should be able to do this directly with rename-in. Take the following code for example:

#lang racket

(require (rename-in racket [this old-this]))

(define this 42)

 (define foo%
  (class object%
    (super-new)
    (define/public (get-me)
      old-this)))

(define bar (new foo%))

(send bar get-me)

Here we use rename-in to import old-this to point to the same binding as this in Racket. Which is needed because we immediately shadow that binding with: (define this 42)

Next, we define the foo% object, with one method: get-me, which just returns the current object with this (or specifically old-this.

Finally, we define a new foo% called bar, and call get-me. As you can see, it returns the bar object as you would expect.

If, instead of old-this, you returned this, then get-me would return 42, because of the definition. Likewise, if you removed the definition for this as 42, then this would still be bound to the one the class macro expects.