Search code examples
classoopobjectracketscoping

Use of the `private` (and `define/private`) form in a Racket class


Racket's classs have a private form that can be used to hide methods. This seems to mimic use of private in other languages like Java or C#. The docs show an example of using private to hide a method:

(define light%
  (class object%
    (super-new)
    (define on? #t)
    (define (toggle) (set! on? (not on?)))
    (private toggle)
    (define (flick) (toggle))
    (public flick)))

> (send (new light%) toggle)
send: no such method
  method name: toggle
  class name: light%
> (send (new light%) flick)

However, by default methods seem to be private, whether or not there actually is a private keyword used. For example, modifying the example from the docs yields the same results:

(define light%
  (class object%
    (super-new)
    (define on? #t)
    (define (toggle) (set! on? (not on?)))
    (define (flick) (toggle))
    (public flick)))

> (send (new light%) toggle)
send: no such method
  method name: toggle
  class name: light%
> (send (new light%) flick)

In both cases, scopes outside of the class don't have access to toggle, but they do for flick, because its marked as public.

So then, what is private used for, and why is it still in the language?


Solution

  • In the second case, toggle is not a method; it is a private field whose value is a closure. In particular, a new toggle closure is allocated for every light% object.

    With (private toggle), the toggle method definition is transformed to take an extra argument for this, and field references are resolved through that argument. Something like this:

    (define (toggle-impl this-obj)
      (set-light%-on?! this-obj (not (light%-on? this-obj))))
    

    Since it no longer closes over this, the procedure can be allocated once, at the same time as the class. The class macro binds toggle to a macro that rewrites all calls to toggle to pass the implicit this argument. (This is why method names are not values; if you want to map a method over a list, you must eta-expand so the method name is in operator position.)