Tuesday, October 7, 2014

Object Oriented Programming Is A Disaster...In The Minds Of Those Who Don't Understand It.

This is rebuttal to the following article:

Object Oriented Programming is an expensive disaster which must end

The above article is full of logical fallacies, and the following text is an attempt to address those fallacies.

OOP is not a good programming paradigm because modern OOP languages do not implement message passing in the way Alan Kay, creator of Smalltalk, envisaged.

The actual implementation of message passing has nothing to do with message passing itself. C++, Java, C#, D, etc provide message passing, in the exact way Alan Kay described.

The key property of message passing as described by Alan Kay is this (taken from the above article):

"every object would be a server offering services whose deployment and discretion depended entirely on the server’s notion of relationship with the servee"

This is exactly how one can view calling methods of objects in C++, Java etc. The idea here is that a message is passed to an object. The details of how this message passing is implemented by a language are irrelevant to the concept of message passing. It does not make a difference, from the point of the caller, how the message is delivered. What matters is that the caller sends a message to the callee.

Of course, different implementation details matter when it comes to performance, latency, response times and correctness of a program, but these have nothing to do with message passing.

One of the key benefits of OOP is static typing.

No one ever, in their right mind, says that. I have never seen this being said, outside of the above-mentioned article. There is no definition anywhere about OOP that includes the words 'static typing'.

Linus Torvalds said C++ is a bad programming language, and therefore OOP is bad.

Torvalds did not say that. He said that C++ is not good language for writing kernels, and that the abstractions C++ offers offer no benefit for kernel writers.

Furthermore, is C++ representative of OOP? it is not. So one cannot say that OOP is bad because C++ is bad, just like one cannot say that FP is bad because C++ is a bad FP language.

OO encapsulation fails to shield us the programmers from the pitfalls of changing state.

The example given is the following class being used in a multithreaded environment, which contains a race condition:

class UniqueId {
    private i = 0;
    function getUniqueId() { return i++; }
}

OOP never promised to shield the programmer from the pitfalls of multithreaded programming. What OOP promises, and delivers, is to shield the programmer from the pitfalls of shared responsibility.

In the above example, fixing the problem is easy because it is only the UniqueId class that is responsible for delivering ids. So, in order to fix the problem, the delivering operation can be managed with a thread synchronization primitive, like a mutex, embedded into the object.

Just because inheritance can be achieved in other languages, we do not need OOP.

OOP does not offer inheritance only. OOP also offers encapsulation and polymorphism.

Put those in your language and you have ...OOP.

OOP is bad because it allows us to have a vast graph of mutable objects, which no human mind can comprehand.

Yes, indeed. And FP programs allows us to write expressions that, if printed, would many hundreds of pages in a book. So?

If one wants to abuse a programming language, he can always do so. No programming language exists that shields the programmer from abusing the language.

OO programming languages are inferior to LISP or other languages because OOP programming languages do not offer multimethods like LISP or those programming languages; and so OOP is inferior to what paradigm LISP or other programming languages represent.

The only reason C++, Java etc do not offer multimethods is performance. When these languages were conceived, performance was of paramount importance. Providing multimethods did not seem important, and, in fact, it is not.

Having a rarely used feature in a programming language does not make that programming language superior to others that do not have that feature.

In fact, the multimethods feature is not really compatible with the feature of a single responsibility: when writing multimethods, there cannot be a single object that is responsible with dealing with a message. It breaks the Actor pattern, that the author used in the beginning of the article to demonstrate that modern OOP is not what OOP should be.

Furthermore, just because an OOP programming language does not offer 100% support for a certain feature does not mean that OOP fails to deliver.

OOP is bad because Inheritance is bad; inheritance allows for large hierarchies.

If you don't want to do large hierarchies, then don't do large hierarchies.
If I did large hierarchies in Closure, which allows inheritance, would Closure be considered a bad programming language? not really.

OOP is bad because Inheritance is bad; inheritance allows for fragile superclasses.

This is a problem in C++ only, where a change in the base class causes a recompilation of all the derived classes.
This is due to the compilation model C++ has, which is that of C: independent translation units linked together by a linker program.
It is much better to pay the price of recompilation than the price of indirection at run time: the price of recompilation is paid only once, at the recompilation phase, whereas the price of indirection is paid many millions of times, each time the program is executed.

OOP is bad because Inheritance is bad; inheritance breaks encapsulation.

The article says that inheritance is bad because it does not allow us to inherit ...binary objects.

Again, this is a problem for C++, that has headers, which is source code.

This problem does not exist in Java, where class files are binary files.

OOP is bad because Inheritance is bad; inheritance does not allow the separation of behavior and types.

What I can say here? the article says that Java does not allow this separation, i.e. that Java doesn't allow type hierarchies that are not bound to any implementation.

Is this deliberate? Java has interfaces, which can form a hierarchy. Doesn't the author know that?
C++ has multiple inheritance that allows interfaces to contain method implementations.

OOP is bad because it does not promote code reuse in the way other languages (like Closure) does.

The author says that Closure has a function 'conj' that works differently for different data types, and that is not possible in OOP.
That is not true, because every collection type in an OOP language could have a function named 'conj', which added an element to the collection.

This has nothing to do with reuse though.

OOP is bad because writing reusable components means to write very small classes, which leads to an explosion of classes in a system.

Not true at all. Even in the most complex OOP systems, there are rarely more than a few hundred of  different classes.
And even then, a comparable in features FP program would have just as many data types, because the classes in the OOP system would represent different concepts.

It goes without saying that if all your data types are based on single linked list, then you don't need many data types.

OOP is bad because ...the Waterfall design process is bad.

Well, here are some news: a design process has nothing to do with OOP. 

OOP can easily be used with any design process, from Agile to Waterfall.

OOP is bad because it does not allow the extension of classes with new functions.

Well, duh, that's the point of object oriented programming: you can't add new functions to a data type willy-nilly. You have to have the source code at hand.

One of the problems with procedural programming was that different programmers added functions that took the same data types as arguments spread all over a code base.
This led to a situation where the functionality build around a data type was scattered to many tens of source files, and more often than not programmers created duplicate functionality in different source files.
This is a lot more important than being able to add new functions to existing data types.
Not only that, but extending data types with new functions in OOP is very easy: just subclass the data types and offer any new functions you want. You don't have to  add any new state to the classes.

The author says, for example, that I cannot extend List in Java with my methods. This is wrong: I can subclass List and add any method that I want. Existing code in binary form will not be affected by my extension, because when that code was written it was not compiled against my extension; therefore, it is only my code that would be affected.

OOP is bad because it makes us declare all our data types in different source files.

Well, no trivial program can afford having data types in a single source file. It does not matter if that program is OOP or FP, putting all the data types into a single source file is simply bad practice. It may work for small projects, but not for large ones.

OOP is bad it forces us to use dependency injection.

It does not. Stop doing that. Please.

In all my years writing object-oriented sofware, I never used dependency injection once.

I never used J2EE, and the few times I was called to write 'enterprise sofware', me and my colleagues avoided J2EE as if it as a plague.

This doesn't mean that OOP is bad.

OOP is bad because ...Java does not have pattern matching like Closure has.

The absurdity continues by presenting a piece of code that uses a series of if-then-else statements in a Java program and pattern matching in Closure, and somehow this must make us believe that OOP is worse than FP.

Well, OOP languages could do pattern matching as well. There is nothing magical in providing a 'match' construct in an OOP language.

OOP is bad because OOP drives us to instantiate objects in the middle of our code, making our code more difficult to test because the code depends on that particular instance we create, whereas in FP we don't have to do that.

This is similar to say that "apples are better than oranges because apples are green whereas oranges are spherical".

The author conflates two different things: testing of an algorithm (or class) with writing an algorithm.

He says this: "the OOP version is worse because if we wanted to test it, we have injected a dependency in it, making testing harder".

How is that different from testing a function that calls another function?

It is not.

It is exactly the same.

Just like the function hasUpperCase() is dependent upon instantiating the class Predicate, the Closure function uppercase depends upon using the class Character.

The Closure version is not more easily tested than the Scala one.

OOP is bad because it makes programmers unable to think of issues like algorithms, concurrency, service composability etc.

Utterly wrong. There are billions of OOP code written that caters for these issues in the best possible way.

OOP is bad because it does not allow us to create services that have minimal downtime (like two hours in 40 years).

Ignoring the fact that no service ever had that downtime, OOP has nothing to do with it.

Erlang is clever in the way that it manages services, but it is not due to the language: the same design pattern can be implemented, let's say, in Java, using messages coded manually (instead of message passing supported by the language).

There is nothing magical in sending keep-alive messages and restarting applications that died.

OOP is bad because it does not allow us to isolate components and manage failure.

Not true. The author gives us the example of Java that does not allow us to run two different applications under the same VM.

Well, if I have two objects in Java that can only send messages and do not expose any other methods, can I run as many objects as I like without the one affecting the other? I can.

Can these objects communicate with sending objects to/from each other? they can.

Can these objects that are sent be immutable? they can: all their members can be final.

So, the style of programming that the author envisages is certainly applicable to Java.

OOP became popular for the wrong reasons.

No, it did not. The author obviously was not around when procedural programming was the mainstream paradigm. 

Object-oriented programming was invented because procedural programming had a lot of problems and applications based on procedural programming did not scale well in size and complexity.

It is 2014, and the sizes of the programs we write has increased 100 times compared to 1984, all thanks to OOP.

OOP failed to deliver reusable software.

No, it did not. There are tens of thousands of OOP libraries reused everyday in millions of programs.

OOP failed to be the silver bullet.

Nothing is a silver bullet.

OOP was an experiment that failed.

Indeed. Billions of OOP code powers our society every day. That's a failure alright :-).


Thursday, November 1, 2012

Why pure functional programming is more difficult than imperative programming.

In this article, the author asks why functional programming isn't more widespread.

I am using functional programming, but in imperative languages. I do not use pure functional programming, because it makes my life more difficult than it ought to be.

The following examples demonstrate clearly my position (in pseudo Java-syntax, to maximize readability).

1) in immutable code, you cannot have mutual pointers. A fine example is parent-child pointers in trees. In imperative languages, it is very simple:

class Node {
    Node parent;
    List<Node> children;
    int data;
}

void addChild(Node child) {
    child.parent = this;
    children.add(child);
}

void printTree(Node node) {
    print(node.data);
    for(Node child : node.children) {
        printTree(child);
    }
}

In pure languages, one has to keep the parent-child relationships in a secondary structure, which is recreated each time it is altered:

class Node {
    int data;
}

class Tree {
    HashMap<Node, Node> parent_to_child;
    HashMap<Node, Node> child_to_parent;
}

Tree addChild(Node parent, Node child) {
    return new Tree(
       parent_to_child.add(parent, child),
       child_to_parent.add(child, parent));
}

void printTree(Tree tree, Node node) {
    print(node.data);
    tree.parent_to_children.filter(node, void (Node child) {
        printTree(child);
    });
}

2) in pure languages, global context must passed around and copied according to what part is changed. For example, in a simple game, the imperative code is:

int score = 0;
Sprite player, playerBullet;
List<Sprite> enemies;

void updatePlayerBullet() {
    for(Sprite enemy : enemies) {
        if (collision(enemy, playerBullet)) {
            enemies.remove(enemy);
            score += 100;
            return;
        }
    }
}

In pure code, the above becomes more complex; the whole game structure must be passed to functions and copied accordingly:

class Game {
    int score = 0;
    Sprite player, playerBullet;
    List<Sprite> enemies;
}

Game updatePlayerBullet(Game game) {
    return updatePlayerBullet(game, head(enemies), tail(enemies));
}

Game updatePlayerBullet(Game game, Sprite enemy, List<Sprite> enemies) {
    if (enemy == null)
        return game;
    else if (collision(enemy, game.playerBullet))
        return new Game(
            score + 100, 
            game.player, 
            game.playerBullet, 
            remove(game.enemies, enemy));
    else
       return updatePlayerBullet(
           game, 
           head(enemies), 
           tail(enemies));
}
 
3) now, let's try to combine the above. Suppose that enemies are trees of sprites, allowing us to destroy part of an enemy with a bullet. The imperative code is straightforward:

class SpriteTree {
    Sprite sprite; 
    SpriteTree parent;
    List<SpriteTree> children;
}

int score = 0;
Sprite player, playerBullet;
List<SpriteTree> enemies;

void updatePlayerBullet() {
    for(SpriteTree enemy : enemies) {
        SpriteTree target = collision(enemy, playerBullet); 
        if (target != null) {
            if (target.parent == null)
                enemies.remove(target);
            else
                target.parent.children.remove(target);
            score += 100;
            return;
        }
    }
}

SpriteTree collision(SpriteTree enemy, Sprite playerBullet) {
    for(SpriteTree child : enemy.children) {
        SpriteTree result = collision(child, playerBullet);
        if (result) return result;
    }
    return collision(enemy.sprite, playerBullet) ? enemy : null;
}

The pure code becomes is much more difficult to write:

class SpriteTree {
    HashMap<Sprite, List<Sprite>> parent_to_children;
    HashMap<Sprite, Sprite> child_to_parent;
}

class Game {
    int score = 0;
    Sprite player, playerBullet;
    List<Sprite> enemies;
    SpriteTree enemyTree;
}

Game updatePlayerBullet(Game game) {
    return updatePlayerBullet(
        game, head(enemies), tail(enemies));
}

Game updatePlayerBullet(Game game, Sprite enemy, List<Sprite> enemies) {
    if (enemy == null) return game;
    Sprite target = collision(
        enemy, game.enemyTree, game.playerBullet);
    if (target == null) return
        updatePlayerBullet(game, head(enemies), tail(enemies));
    Sprite parent = getValue(game.enemyTree, target);
    int newScore = score + 100;
    List<Sprite> newEnemyList = 
        parent != null ? 
        game.enemies : 
        remove(game.enemies, enemy);
    SpriteTree newEnemyTree = 
        parent != null ? 
        remove(game.enemyTree, parent, child) :    
        game.enemyTree;
    return new Game(
        newScore, 
        game.player, 
        game.playerBullet, 
        newEnemyList, 
        newEnemyTree);
   }
}

Sprite collision(Sprite enemy, 
                 SpriteTree enemyTree, 
                 Sprite playerBullet)
{
    List<Sprite> children = getValue(enemyTree, enemy); 
    if (children != null) {
        Sprite childEnemySprite = collision(
            head(children), 
            tail(children), 
            enemyTree, 
            playerBullet);
        if (childEnemySprite != null) 
            return childEnemySprite;
    }
    return collision(enemy.sprite, playerBullet) ? 
        enemy : 
        null; 
}

Sprite collision(Sprite enemy, 
                 List<Sprite> enemies, 
                 SpriteTree enemyTree, 
                 Sprite playerBullet)
{
    if (enemy == null) return null;
    Sprite result = 
        collision(enemy, enemyTree, playerBullet);
    return 
        result != null ? 
        result : 
            collision(head(enemies), 
                tail(enemies), 
                enemyTree, 
                playerBullet);
}

As you can see, the pure code spans many more lines of code than the imperative code; it's also more complex than the imperative code: the structures required to support the operations must be passed around all functions, even if not used by that specific function.

Now multiply the complexity of the state by one million (easily modern applications have a million times more state than this example), and you can see why people don't program in pure functional languages: a complex and hugely stateful program may become such a pure functional puzzle that no one could even begin to solve it.