I’m not against objects. I’m against programming in a style oriented by objects.
One of the largest causes of big-ball-of-mud architectures is unnecessary cyclic dependencies. Unfortunately, object-oriented languages allow programmers to trivially create cyclic dependencies without warning or immediate repercussions. Overtime, these cycles become a significant source of complexity and corresponding suffering.
The problem can be summarized in a single line of pseudocode:
This innocuous-looking expression embodies a pleasant interface, but its
implementation hides an insidious cyclic relationship between the
class and the
Address class. Worse than that, this cyclic relationship
is extended transitively to any other related classes. Consider the following
diagram with an additional
Company class that may also have an address.
Any time you add a type to this graph, it is instantly tangled-up with everything else. Compare this with this functional variant:
Now you can analyze, compile, and reason about a subset of this graph. Reasoning about any particular node only requires reasoning about the transitive closure of its dependencies. However, in the presence of ubiquitous cycles, the transitive closure is equivalent to the entire graph.
Unfortunately, we’ve now lost the convenient
syntax. Instead, our expressions are inside out. Here it is in Ruby or Python
And here it is in Lisp syntax:
Luckily, we can recover the pleasant ordering with the help of a macro. Here is an example of Clojure’s threading macro:
In the absense of macros, we can use a binary operator. Here’s an example of F#’s “pipeline” operator:
Both Scala and C# provide tools for directly emulating the dotted access syntax. Respectively, implicit conversions and extension methods leverage the type system to perform dispatch to non-members in a syntactically identical way.
It is unfortunate that it is so easy to create cycles. A small sin at first, but they quickly add up and really start to hurt as a system grows larger.