@ Loup's

Impossible? Like that would stop me.

July 2015

Enough with paradigms!

Paradigms generally sound like "[something] programming". Procedural programming, structured programming, functional programming, object oriented programming, logic programming, generic programming, tabular programming (yes, I've heard that one)…

Intuitively, a paradigm is a set of tools and practices. Procedural programming uses procedures to separate the different parts of the code. Structured programming avoids the goto statement in favour of more disciplined control structures such as if and while. Functional programming tries to avoid or segregate side effects and use higher order functions liberally. Object oriented programming… I don't know. (Well, nobody does.)

The problem is getting stuck in your paradigm of choice. Like sticking to structured programming to describe a finite state automaton (those are easier to describe with lots of goto statements), or insisting on OOP to write a parser.

This is most visible with OOP, because of its sheer ubiquity. I'll use it as an example.

The OOP myth

First we had procedural programming. Then OOP came and made things better. It increased modularity, flexibility, and code reuse. Nowadays, Many programmers choose OOP for everything, right tool for the job be damned.

This hype isn't entirely unfounded. OOP did have 3 big advantages: encapsulation, dynamic dispatch, and inheritance. But then a few things happened:

Encapsulation: Stolen!

Encapsulation is about separating public interface from implementation detail. By accessing stuff through the interface alone, you are prevented from doing a number of mistakes, such as breaking a object's invariants. This lets you build abstractions (stack, queue, search tree…) without explicit language support.

In C++ and Java, encapsulation simply means distinguishing private members from public methods. In Smalltalk, instance variables are all hidden, and you have to use methods. The first language that let you hide objects' attributes was Simula (1967), the first object oriented language.

But then it got adopted by non-OOP languages:

Note that encapsulation as described here is not the only way to separate interface from implementation. That separation was already present in the humble function. Functions can be called, but not inspected. You can observe their behaviour, but you'd have to read their source code to see their implementation.

Dynamic dispatch: Predated!

Dynamic dispatch is about parametrizing over behaviour. You invoke a piece of behaviour, but you don't know which at compile time. That decision is deferred at run time. Here is an example in Python:

def foo(bar):
    bar.baz()

The function foo() does not know where the method baz() comes from at compile time. That's determined at run time, when an actual object (containing an actual baz() method) is provided. Note that C++ and Java can basically do the same, there's just more boilerplate involved (they use inheritance and virtual tables instead of duck typing).

If you have first class functions, the behaviour you invoke doesn't have to be hidden behind an object. Again, in Python:

def foo(baz):
    baz()

I won't insist on why dynamic dispatch is such a big deal. Suffice to say, it was the advantage of OOP over plain procedural programming. This time however, Simula wasn't the first to provide it. Lisp already had first class functions in 1958. And now, every modern language supports some form of dynamic dispatch (either through virtual methods (C++, Java), duck typing (Python), prototypes (Self, Javascript), or first class functions (Lisp, Javascript, Python, ML, Haskell, Java, C++…). Even C has limited support for dynamic dispatch, thanks to its function pointers.

Inheritance: Rejected!

Inheritance is the only significant feature of OOP that hasn't been widely adopted elsewhere. For good reason. It doesn't really help with code reuse, and it breaks encapsulation. Now, composition is preferred.

I know of one solid use case for inheritance: enabling dynamic dispatch in statically typed class based languages, thanks to subtype polymorphism. Though even then, nearly every modern language supports closures, making subtype polymorphism mostly unneeded.

And if you're a language designer, nothing stops you from separating implementation inheritance from interface subtyping. Like Java's interfaces, except any class may be treated like an interface. You would get the benefits of subtyping without the problems of inheritance.

Another nice use case I stumbled upon for inheritance was formal grammars. I think this works because in this case, there is no encapsulation to break: every rule is effectively public.

Adopted out of existence

On the one hand, I think OOP should die. It is overused, and often encourages bad practices. Yet I still meet programmers who disparage code for not being of their favourite flavour of OOP. Good code does not mean "OOP", (nor "Functional" for that matter). It mostly means short and modular.

On the other hand, encapsulation and dynamic dispatch are wonderful. Even inheritance is useful sometimes. I don't want to throw out the baby with the bathwater.

If OOP ever dies, I want to feast on its corpse.

Kill all the paradigms!

Then feast on their corpses.

Who cares about functional programming when you just want referential transparency? Who cares about generic programming when you just need templates? Who cares about logic programming when you can pick an inference engine?

Some would talk about "multi-paradigm" programming, but that conveys the wrong idea: in practice we don't combine paradigms, we cherry-pick mechanisms. That's not using multiple paradigms, that's just not giving a damn.

Which is a good thing: when our mind is freed from the shackles of paradigms, we can stop doing OOP, FP, or whatever is trendy, and focus on writing good code instead.

Mechanisms over paradigms.