22 Overcharge: Objects, but it’ll cost you
22.1 Treating things as objects
Many of the most popular programming languages use Objects as a way to organize code and provide opportunities for abstraction. One of the key ideas that objects provide is encapsulation: The idea that we can group data with the functionality for working on/with that data. The functionality that’s been packaged with the data is known as a method.
This set of lecture notes is not meant to be comprehensive, instead it is meant to give a high-level view of how you might implement objects, starting from a language like Loot.
22.2 Long Live Lambda!
One way to think about objects is that it’s a package of data along with code (the methods). This is just the other side of how we implement lambdas: closures which have data associated with code. We will explore this relationship later on.
22.3 Method to the madness
Methods are functions that have implicit access to a particular object’s data. Often we call the particular object this. Which means that however we implement objects, we have to make sure that this scoping is respected.
22.4 Student Wish List
In lecture, we discussed how we expect objects to behave, and what features we expect. This is the list of things we came up with:
Container with fields and methods
Inheritance
Environment where the fields are bound
Notion of mutability for fields
Static variables
Organizing data with sophisticated type system
Stored on the heap (implementation)
Notion of access control (public vs private fields)
One distinction we made was that between a class (the ’type’ of an object) and the object itself (the actual value that we construct/manipulate).
22.5 A concrete syntax for classes
(class (dog n w b) (name (if (= b "yorkshire") (append "sir " n) n)) (weight (to-metric w)) (breed (lower-case b)) (bark (lambda () (breed-volume breed "bark!"))))
Above we are doing a few things:
Declaring and defining the constructor dog which creates an object
Defining the fields, which can use the constructor arguments in their definition
Notice that we aren’t making a real distinction between fields and methods: methods are just function-valued fields!
22.6 A common refrain
You may remember this from our Loot notes:
Q: How can we represent strings?
A: With strings!
Q: How can we represent booleans?
A: With booleans!
Q: How can we represent numbers?
A: With numbers!
Q: How can we represent pairs?
A: With pairs!
Q: etc.
A: etc.
Q: How can we represent functions?
A: With functions!?
Q: How can we represent methods?
A: With functions!?
Q: Wait, what?
It turns out that we can leverage the way that closures are implemented in the heap in order to implement objects. Given the example class above, we could translate it to the following function definition:
(racketblock (define (dog n w b) (let ((name (lambda () (if (= b "yorkshire") (append "sir " n) n)) (weight (lambda () (weight (to-metric w)))) (breed (lambda () (breed (lower-case b)))) (bark (lambda () (breed-volume breed "bark!"))))) (lambda (field . xs) (match field [’name (apply name xs)] [’weight (apply weight xs)] [’breed (apply breed xs)] [’bark (apply bark xs)])))) )
Let’s start from the end:
We’re creating a lambda that takes a symbol representing the field we want, and the rest of the arguments as a list. We dispatch on the field, deciding which field we want to access out of this lambda.
Each field has been converted into a lambda that captures the environment that it needs (from the constructor’s arguments). When we (apply name xs) we are calling the lambda that represents the field.
22.7 Things to consider:
How would we add mutation?
Is mutation required for Object-oriented programming?