Object-oriented Programming

We can look at closures as an impoverished kind of object, and in another POV look at objects as an impoverished kind of closure. You should for now be able to see the superficial similarity of a closure to an object: both conceal encapsulated private data, and require any access to those data be mediated through use of a publicly exposed method. The trick is that with a closure, there only ever is one method.

Class-based OO, vs. prototype-based OO.

We will not be simulating a type system. So our OO simulation will not be a class-based OO language, using subclassing for inheritance. Instead, we will use a technique called delegation: when this object doesn’t know what to do with a given received message, it contains with it already a “plan in mind” for which object to instead ask to handle that message.

A couple of neat techniques

There are a couple of neat techniques that you need to know about for some of this discussion to make sense:

Variable-arity functions, or varargs.

The Java programmers in the room already know about how, in some cases, Java function parameters are wrapped up into an array, and the function will expect that single array of parameters instead of however many it was called with. Java does this because they want, say, some given function to be able to handle varying-numbers of arguments, and the user of that function shouldn’t pay any price for the generality. A user should be able to call the function like any other 2 argument function. Racket and it’s ancestors have had this feature for generations.

(lambda args args)

The below is a well-defined function. Notice that we do not write parens around args in the first position. That symbol args is supposed to correspond not to a single argument in a list of parameters, but to the entire parameter list!

> ((lambda args args) 'a 'b 'c (+ 2 3))
'(a b c 5)

So it turns out not only is this an exploitable feature, it’s immediately useful! This is an implementation of list in Racket!

If you want to say “one or more arguments”, instead of “any number of arguments”, you can use the “dot notation” that we’re used to for cons lists in the function parameters list.

(define f
  (lambda (a . res)
    (cons res a)))

And of course you justly can and should be able to nest functions into the “MIT-define” syntax.

(define ((((f) a . res)) . more)
  (append res (cons a more)))

And you call the function just like you’d expect.

“own” variables.

You should like and be happy to know that define is not special syntax that for instance only works with lambda. You can construct a let binding in between and around that.

(define f
  (let ((v 'my-secret-symbol))
    (lambda (x)
	  (eqv? x v))))

And the point is that these are called “own” variables, because they are local to the defined function f, and not globally visible or visible to any other scope, but they are also not parameters to the function. It’s a way to give functions their own lexically scoped private environment data.

By the by, since, as you know, named lets also let you approximate a function—that is, they are not just labels but instead functions in their own right that you can return as values, you get some more neat properties. Just fun to think about

(define f
  (let loop ((v 'my-secret-symbol))
    (lambda (x)
	  (if (eqv? x y)
	      f
          (begin
		    (set! v x)
			loop)))))

apply

Another powerful lisp feature that we haven’t yet called upon in this class is apply You will occasionally find yourself in a situation where you have a variable arity function, and a list of the parameters with which you want to call it.

(define (call-it var-arity-f list-of-args)
  ...)

But it’s tough to figure out what to do from here. Because what you want to get is a call that looks like (f arg1 arg2 arg3 ... argn). But I don’t know how many elements are in that list of args, so I couldn’t build

(f (car ls) (cadr ls) ... (caaaaaaaaaadr ls))

even if I wanted to, which we don’t.

So what we do here is use apply, a special-built tool that will, when given a function and a list of arguments, call that function with those arguments as the parameter list.

Boxing

So we’ll start out by building a box-maker. We are simulating with Racket lambda and a couple of side-effects, which we somewhat control. If your head is thinking “could I get by with something like this without Racket side-effects?” I like where your head is at but that’s not today’s point.

(define (box-maker init-val)
  (let ([contents init-val])
	(λ msg
	  (match msg
		[`(type) "box"]
        [`(show) contents]
		[`(update! ,x) (set! contents x)]
		[`(reset!) (set! contents init-val)]
		[else (delegate base-object msg)]))))

The box-maker function is our constructor, and the function that it returns is how we represent our boxes. We represent our box objects using Racket closures. We simpulate the ability to invoke multiple different methods, with different numbers of parameters each, by using variable-arity functions and dispatching against the shape of the message itself. Our implementation of boxes has something that the typical Racket variant doesn’t have—a reset! method back to the original value with which the box was constructed.

We have not yet implemented delegate, base-obj or an ability to send along a message. Let us do that now.

(define delegate
  (λ (obj msg)
    (apply obj msg)))

(define invalid-method-name-indicator "unknown")

(define base-object
  (λ msg
    (match msg
      [`(type) "base object"]
      [else invalid-method-name-indicator])))

(define send
  (λ args
    (let ((obj (car args))
          (msg (cdr args)))
      (let ((try (apply object message)))
        (if (eq? invalid-method-name-indicator try)
            (error 'send "bad method name ~s sent to a ~s" (car message) (object 'type))
            try)))))

Updated: