The mysterious tale of missing objects

Pawel Blazejewski
Grand Parade
Published in
7 min readDec 4, 2018

--

Nubi asked once master Foo: How many objects do I need to make my program Object Oriented?

Master Foo smiled. None!

Upon hearing this, Nubi was enlightened.

Let’s have a look at the following code. That is C, it is not object-oriented even though it has “objects”. Thus the object does not mean object-oriented.

struct point {
int x;
int y;
};
point p = {
.x = 2,
.y = 3,
};

The goal of this article is to prove that the idea behind OOP is not about classes objects etc, but rather about some basic principles. If your language is good enough you could follow those principles without any objects. Plain functions are good enough.

The three pillars of OOP are:

Encapsulation * Inheritance * Polymorphism

So let's try to implement them all without objects, classes, nothing at all, except functions and variables.

Pros of Cons

The first thing we need is to create compound objects using only functions. The simplest structure possible is a “cons” or a pair of two entities.

const Pair = (a, b) => message => 
message === 'head' ? a : b;
const pair = Pair('first', 'second');pair('head'); // returns "first"
pair('tail'); // returns "second"

The “pair” is such entity and you can access its parts using the technique called a message passing. The message is used to choose the behavior of a function and make it return a particular value.

That was the toughest part, the half of a work is done and it’s about time to have a good cup of tea. ☕

Now, if we know how to connect two parts, connecting more of them is easy. Let’s create an “object” alternative to the

{one: 1, two: 2, three: 3}

I’m going to use the following form of a constructor

Obj(key_1, value_1, key_1, value_2, ...)

So that the odd parameters are keys and even parameters are values. Just search for a key in keys and return the value with the same index.

// filter of odds and evens
const
odd = args => args.filter((_, i) => i % 2);
const even = args => args.filter((_, i) => !(i % 2));
// the constructor of an objects
const Obj
= (...args) =>
message => odd(args)[even(args).indexOf(message)];
const something = Obj(
'one', 1,
'two', 2,
'method', ()=>{}
);
something('one'); // 1
something('two'); // 2
something('three'); // undefined
something('method'); // function

You see? To have an object you don’t need an object at all. It was easy and now the exact time to start a serious staff. The first stop on that journey is…

The first pillar

Encapsulation

We need to create a function constructor which is able to create “objects” with private and public properties. So we are going to create the analogy to Java’s:

public class Person{
private final string greeting = "Hello";
private string name;
public Person(string name){
this.name = name;
}
public string getName(){
return name;
}
public string setName(string name){
return this.name = name;
}
public void greet(string name){
log(greet + " " + name + "!";
}
}

That is not that difficult as Java is trying to make us believe. We need to pass some parameters to the constructor and use message passing to call methods.

const Person = name => {           // Constructor parameter

const greeting = 'Hello'; // A private field

return (message, ...args) => { // The message-passing interface
switch(message){ // dispatching methods
case 'get-name':
return name;
case 'set-name':
return name = args[0]
case 'greet':
return console.log(`${greeting} ${name}!`);
default:
console.error(
`Reference error.
Method "${message}" was not found.`
);
}
}
};
const me = Person('Pawel');
me('greet');
me('set-name', 'Paul');
console.log('-->', me('get-name'));
me('greet');
me('bye');
/*
The output is
Hello Pawel!
--> Paul
Hello Paul!
Reference error. Method "bye" was not found.
*/

It works. But the biggest part of the “class” is just a giant switch. It is going to be common for all constructors and has to be extracted. That switch even has its own name. It is a “dispatcher”. It dispatches a message to the function. Actually, it could be implemented as a lookup table, an array, a map or whatever.

So let’s extract it and use our object constructor to store a lookup table.

const odd = list => list.filter((_, i) => i % 2);
const even = list => list.filter((_, i) => !(i % 2));
const Obj = (...args) =>
message => odd(args)[even(args).indexOf(message)];
const dispatch = (...methods) =>
(message, ...args) => {
const method = Obj(...methods)(message);
return method
? method(...args)
: console.error(
`Reference error.
Method "${message}" was not found.`
);
}
const Person = name => {
const _greeting = 'Hello';
let _name = name;
return dispatch(
'get-name', () => _name,
'set-name', name => _name = name,
'greet', () => console.log(`${_greeting} ${_name}!`),
);
};

The “dispatch” function gets as a parameter a lookup table and maps a message to the corresponding method. By the way, as we see later, the table could have more than one level of mapping.

For now, we created a constructor of objects which accepts parameters. We could define private and public fields and methods.

  • Do you want to implement “protected”? Many different levels of protection? Perhaps just use Symbols as a message.

Encapsulation — done.

More tea ☕☕?

The second pillar

Inheritance

In object-oriented programming, inheritance is the mechanism of basing an object or class upon another object (prototypical inheritance) or class (class-based inheritance), retaining similar implementation.

public class Person {…}
public class Woman extends Person {…}

To implement the prototypical inheritance we need to change our dispatch function. It has to accept another argument: an instance of a super “class”.

const dispatch = proto => (...methods) => (message, ...args)  => {
const method = Obj(...methods)(message);
if(method){
return method(...args);
} else if(proto) {
return proto(message, ...args);
} else {
console.error(
`Reference error. Method "${message}" was not found.`
);
}
}

Just an additional “if” branch. If a method was not found in the table, search in the prototype. Now the modification of the dispatch call. Pass the prototype to the dispatch.

const Person = name => {
const _greeting = 'Hello';
let _name = name;
const extend = dispatch(); return extend(
'get-name', () => _name,
'set-name', name => _name = name,
'greet', () => console.log(`${_greeting} ${_name}!`),
);
};
const Woman = name => {
const _bye = 'Good bye...';
const extend = dispatch(Person(name)); // <-- inheritance return extend(
'bye', () => console.log(_bye),
);
};
const her = Woman('?');
her('set-name', 'Oksana');
her('greet');
her('bye');
//Output
Hello Oksana!
Good bye...
  • Could you do a multiple inheritance? Just use a list of prototypes.
  • Could you dynamically change an inheritance chain? Just replace the prototype.

The third pillar

Polymorphism

We need to be able to override one method and let it use the other parts of a super “class”. That is super easy, just use getters instead of variables.

const Person = name => {
const _greeting = 'Hello';
let _name = name;
const extend = dispatch(); const self = extend(
'get-name', () => _name,
'set-name', name => _name = name,
'greet', () =>
console.log(`${_greeting} ${self('get-name')}!`),
);
return self;
};
const Woman = name => {
const _bye = 'Good bye';
const _greeting = 'Hi';
const extend = dispatch(Person(name)) const self = extend(
'greet', () =>
console.log(`${_greeting} ${self('get-name')}!`),
'bye', () =>
console.log(`${_bye} ${self('get-name')}!`),
);
return self;
};

Pillar 3/4

Typing, aka static polymorphism

And now, we are going to introduce THE THING which only exists in the “adult” programming languages, the function typing. Super easy just a minor modification of a dispatching table.

const dispatch = proto => (...methods) => (message, ...args)  => {
const type = `${args.map(arg => typeof arg).join(' ')}`;
const method = Obj(...methods)(`${message}:${type}`); if(method){
return method(...args);
} else if(proto){
return parent(message, ...args);
} else {
console.error(
`Reference error.
Method "${message}" of type "${type}" was not found.`
);
}
}
const Adder = () => {
const extend = dispatch(null);
const self = extend(
'add:number number', (a, b) => a + b,
'add:string string', (a, b) => `${a} and ${b}`,
);
return self;
};
const adder = Adder();
adder('add', '6', '6'); // "6 and 6"
adder('add', 6, 6); // 12
adder('add', '6', 6); // Reference error.
// Method "add" of type "string number"
// was not found.

That is just a slightly more complex dispatching table. And if you need to dispatch a function not only by input parameters but by some other condition, it’s easy, right?

  • Could you use in dispatch the type of the return value of a method? Just add another dimension to the dispatch table.
  • Could you call a default method when nothing found? Easy right?
  • Could you introduce a complex syntax of a message and generate the method basing on the message? Doable.
  • Could you create your flavor of OOP? Just ten more lines of code.

I don’t know how many strings of Java code you need to introduce the multiple inheritance, but if your language is as flexible as JavaScript, you could have whatever paradigm you want with a bare minimum. By the way the idea of message passing is much older than JavaScript itself.

Nubi asked Master Foo to give him enough numbers to do math.

Master Foo gave him () => {} and said it is 1.

On hearing this…

--

--