Some thinking over at Quora : What’s wrong with OO?
Continuing some thoughts that have been haunting me since I originally phrased them in the answer about designing the perfect programming language : Phil Jones’s answer to How would you design the perfect programming language?.
And in response to Alan Mellor‘s comment on Phil Jones’s answer to In what direction computer science is going for next 10 years? where he says he’d prefer to program a “device swarm” in OOP.
There’s nothing wrong with OO.
Considering the kind of OO you get in Smalltalk (which is really the only OO worth thinking about), it’s a beautifully simple and elegant and powerful paradigm for programming.
The problem is OO languages.
As I put it in my answer :
It’s that mismatch between a program that thinks of itself as a network of interacting objects, and a language that has no explicit concept of “a network of interacting objects” and no convenient way to express it, that makes Java and friends such hard work. Somewhere deep in the heart of the UML is a good intuition, struggling to find a viable implementation.
These days I’m all about languages for declaring / representing data. OO is a fantastic semantics / virtual machine for thinking about programs. And it’s even fine for data. Data is just a network or assemblage of interconnected objects.
BUT …
No OO language has such an explicit concept of network or assemblage. They all stop at the level of class. Ie. the generalized form of a single object type.
But why does this need to be the case?
Why can’t we have a way of declaring a collection of classes / objects in one go?
I started thinking in terms of JSON or EDN-like notations.
But there’s an obvious problem. Object networks are fundamentally networks (or lattices) not hierarchies.
But all our notations for serializing complex data-structures into a linear text file can really only handle hierarchical data. You use indentation or brackets or XML-style tags to indicated sub-levels of the tree.
But we all know that the connections between objects in a network or system break out of hierarchies and demand a “anything can connect to anything” structure. And that becomes awkward.
Either you go for a special “network drawing” tools of the kind that UML goes for. Or you have some arbitrary and ugly way to define the hierarchy breaking connections.
But then a couple of days ago I was watching a talk which briefly covered the ML / Haskell family of languages’ algebraic data-types. Which are great for defining complex data schemas in a way that’s almost like Backus-Naur or grammatical definitions.
And it occurred to me that these would work pretty well for an assemblage of objects too.
So here’s the question … could we add some higher level aggregate command to our OO languages that looks something like this. I’m going to call this “Pattern”. But maybe “Schema” or “Assemblage” would be better.
Here goes :
Pattern make: #CarPattern from:
Car = Engine Chassi Wheel[4] ModelName
Engine = noCylinders horsepower
Chassi = width depth weight
Wheel = diameter
ModelName = string.
This is a pretty standard schema-like definition. And in Haskell you often see the algebraic types defined together so you can quickly read such a schema.
Whereas in pretty much every OO language in practice, there is so much emphasis on individual classes that the wider schema gets lost. You can’t see the forest for the trees. That’s true of Java which forces you to put each class definition in a separate file, but also Smalltalk where the Class Browser puts each class in its place in the hierarchy, usually based on type rather than affinity.
So, in my example, on compilation or execution (depending on the kind of language this is) this Pattern definition would produce a number of classes : Car, Engine, Chassi, Wheels, ModelName, with instance variables based on their terminals.
But these objects also have slots or instance variables for the components. Or in the case of Wheel, there’s a Wheel class, but the Car class has a collection or array of four of these Wheels.
I’ve long thought that it would be useful for a language to have an explicit distinction between slots containing objects that are components “internal” to an object, and slots containing other external objects that are lent to it, or that represent things outside itself.
Here I’ve added a new notation. By default the grammar defines components. But a + sigil at the front of the name makes something a reference to an external object.
Pattern make: #SchoolPattern from:
School = Classroom[] Teacher[] Pupil[] Curriculum
Classroom = id
Teacher = Name employeeNumber
Pupil = Name studentNumber
Curriculum = SchoolClass[]
SchoolClass = +Teacher +Classroom +Pupil[] name hour
Name = firstName lastName.
Note : I’ve used SchoolClass rather than Class to avoid ambiguity with the OO notion of Classes. Here it’s a collection of Students in a Classroom with a particular Teacher at a particular hour.
But our notation is explicit that these objects stored in the SchoolClass slots come from elsewhere in the system. They are not internal to, or limited to a particular SchoolClass. They can appear outside any particular SchoolClass. Perhaps there’s a Teacher who isn’t currently teaching anything this semester.
Now what about or?
Pattern make: #Vehicle from:
Vehicle = Car | Truck | Bus.
In a statically typed language like Haskell, or even a statically typed OO language like Java, this kind of feature would be meaningful. In a dynamically typed Smalltalk or Python maybe not so much but there could be value to it.
I’m in favour of languages that try to eliminate null values.
We could certainly imagine a Smalltalk-like that doesn’t allow nulls in slots by default. But has some kind of Maybe or Optional for when it’s necessary. And this can be a sigil too.
Let’s go with ?
Pattern make: #Student from:
Student = id Name email ?whatsapp
Name = firstName lastName.
Here the whatsapp value is explicitly wrapped in however the language thinks about Maybes.
And we can imagine a language with default immutability but sigils again used for explicit mutability.
We’ll use @ for mutable instance variables.
Pattern make: #PongPattern from:
Pong = Paddle[2] Ball
Paddle = x @y height color
Ball = @x @y @dx @dy color radius.
Immutable instance variables like the x co-ord of the Paddles and the colours of everything on screen are set at construction time. Only the y of the paddle and the x, y, dx and dy of the ball can change at a later time.
So how do we construct and instantiate a pattern?
Well, we already have a basic grammar for our model which can now be pressed into service in serializing / deserializing the data from our collection of objects into some kind of data-structure.
Everything which is internal must eventually bottom out as primitive numbers and strings or simple objects.
So, for example, using a hiccup-like notation :
PongPattern :constructAll
[:Pong [:Paddle 10 250 50 (Color new: 255 100 100)]
[:Paddle 490 250 50 (Color new: 100 100 255)]
[:Ball 250 250 1 -1 (Color new: 100 255 100) 4] ]
Note that parsing data-structures is a place where even in a dynamically typed language the | is useful.
Anything external must be passed in as an object reference.
What about abstract classes and interfaces? In one sense, the algebraic data gives you that for free. In a statically typed language, a Vehicle would be something that could either be a Car or a Truck or a Bus.
What about adding methods to classes?
This would be in a separate section of the schema definition. And work more like C++ or (as I understand as I’ve never used it) CLOS.
Something like :
Pattern make: #PongPattern from:
Pong = Paddle[2] Ball
Paddle = x @y height color @score
Ball = @x @y @dx @dy color radius
methods:
Ball:
testAgainst: player1 and: player2
(self @y < 3) ifTrue: [@dy = 1].
(self @y > 497) ifTrue: [@dy = -1].
(self collideWith: player1) ifTrue: [@dx = 1].
(self collideWith: player2) ifTrue: [@dx = -1].
(self @x < 0) ifTrue: [ player2 incScore.
self @x := 250.
self @y := 250.
].
(self @x > 500) ifTrue: [ player1 incScore.
self @x := 250.
self @y := 250.
].
etc.
Anyway … that’s the first braindump.
Just to note. This isn’t meant to be changing the semantics of a running OO system. When Patterns are made they just turn into Classes. When Patterns are constructed instantiated we create and instantiate ordinary Objects.
It’s just that this gives us a way of making the concept of network or assemblage of objects explicit within the language. And helps save us from what seems to make OO so wearisome. The fiddling with fragments. The dreaded “Ravioli code”
Leave a Reply
You must be logged in to post a comment.