What is an object?

When a language admits functions as values, it provides developers the most natural way to represent a unit of computation. Suppose a developer wants to parameterize some function f. Any language lets f be parameterized by passive data, such as numbers and strings. But it is often attractive to parameterize it over active data: a datum that can compute an answer, perhaps in response to some information. Furthermore, the function passed to f can—assuming lexically-scoped functions—refer to data from the caller without those data having to be revealed to f, thus providing a foundation for security and privacy. Thus, lexically-scoped functions are central to the design of many secure programming techniques.

While a function is a splendid thing, it suffers from excessive terseness. Sometimes we might want multiple functions to all close over to the same shared data. Let’s add a notion of objects to our language.

An object is meant to encapsulate in a coherent whole a piece of state (possibly, but not necessarily, mutable) together with some behavior that relies on that state. The state is usually called fields (or instance variables), and the behavior is provided as a set of methods. Calling a method is often considered as message passing: we send a message to an object, and if it understands it, it executes the associated method.

In Paret we can have similar creatures such as:

(define add
  (lambda (n)
    (lambda (m)
      (+ m n))))

 
> (define add2 (add 2))
> (add2 5)

7

The function add2 encapsulates some hidden state (n = 2) and its behavior effectively depends on that state. So, in a sense, a closure is an object whose fields are its free variables. What about behavior? well, it has a unique behavior, triggered when the function is applied (from a message passing viewpoint, apply is the only message understood by a function).

Paret supports mutation (set), so we can effectively have a stateful function with changing state:

(let ((counter
				(let ((count 0))
					(lambda ()
						(set count (add1 count))))))
	counter)

We can now effectively observe that the state of counter changes:

> (counter)
1
> (counter)
2

A similar program in JavaScript is:

function mk_counter() {
	var count = 0
	return () => count = count + 1; count
}

counter = mk_counter()
counter() // 1
counter() // 2

Now, what if we want a bi-directional counter? The function must be able to do either +1 or -1 on its state depending on... well, an argument!

(let ((counter
				(let ((count 0))
					(lambda (cmd)
						(cond
							((str= cmd "dec") (set count (sub1 count)))
							((str= cmd "inc") (set count (add1 count)))
							(else
								(error
									(concat "invalid command: " cmd))))))))
	counter)

where:

Untitled

Note how counter uses cmd to discriminate what action to perform.

> (counter 'inc)
1
> (counter 'dec)
0