Some time ago, Paul Graham wrote an essay with the title “Why Arc isn’t especially object-oriented“, which contains some interesting arguments against object-oriented programming. Jonathan Rees’ comments on the essay are also worth reading.
To my mind, what makes object-oriented programming problematic is its complex denotational semantics. As far as I know, the state of the art of semantics for object-oriented programming is A Theory of Objects by Abadi and Cardelli. The complexity of the semantics of (recursive) object calculi is daunting in comparison with the semantics of, e.g., simply typed lambda calculus (or intuitionistic type theory for that matter).
In essence, the problem with the object-oriented paradigm of programming is that it allows for (and encourages) programs that make use of mutually recursive object classes. It is not unusual that the whole object model of a program has to be understood as a whole. Because of the tight coupling between object classes, the semantics of object-oriented programs is noncompositional (though, of course, one can program in an object-oriented language without making use of its noncompositional features). A lack of compositionality leads to a lack of reusability:
“I think the lack of reusability comes in object-oriented languages, not in functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.” — Joe Armstrong, quoted by Seibel in Coders at Work (2009).
However, as Jonathan Rees writes in the post cited above, object-orientation is not an entirely well-defined concept. He gives several examples of object-orientation being defined as subsets of the following list:
- “Encapsulation – the ability to syntactically hide the implementation of a type. E.g. in C or Pascal you always know whether something is a struct or an array, but in CLU and Java you can hide the difference.
- Protection – the inability of the client of a type to detect its implementation. This guarantees that a behavior-preserving change to an implementation will not break its clients, and also makes sure that things like passwords don’t leak out.
- Ad hoc polymorphism – functions and data structures with parameters that can take on values of many different types.
- Parametric polymorphism – functions and data structures that parameterize over arbitrary values (e.g. list of anything). ML and Lisp both have this. Java doesn’t quite because of its non-Object types.
- Everything is an object – all values are objects. True in Smalltalk (?) but not in Java (because of int and friends).
- All you can do is send a message (AYCDISAM) = Actors model. – there is no direct manipulation of objects, only communication with (or invocation of) them. The presence of fields in Java violates this.
- Specification inheritance = subtyping – there are distinct types known to the language with the property that a value of one type is as good as a value of another for the purposes of type correctness. (E.g. Java interface inheritance.)
- Implementation inheritance/reuse – having written one pile of code, a similar pile (e.g. a superset) can be generated in a controlled manner, i.e. the code doesn’t have to be copied and edited. A limited and peculiar kind of abstraction. (E.g. Java class inheritance.)
- Sum-of-product-of-function pattern – objects are (in effect) restricted to be functions that take as first argument a distinguished method key argument that is drawn from a finite set of simple names.”
In intuitionistic type theory, these nine points can be interpreted as follows.
- Encapsulation. In intuitionistic type theory, encapsulation can be achieved in the three ways: (1) as in abstract algebra, (2) by using a module system, and (3) by using interfaces.
- Protection. Protection is achieved much in the same way as encapsulation.
- Ad hoc polymorphism. An implementation of intuitionistic type theory can support ad hoc polymorphism.
- Parametric polymorphism. Parametric polymorphism is an application of dependent types. That is, any implementation of intuitionistic type theory has to support parametric polymorphism.
- Everything is an object. In intuitionistic type theory, a value is usually defined as a canonical object, and every entity dealt with is either a type or an object (though, the type-theoretic notion of object differs from that of object-oriented programming).
- All you can do is send a message (AYCDISAM). This is true in a very strict sense in the component model of intuitionistic type theory. A component can only send messages on its required interface.
- Specification inheritance. Subtyping is usually not implemented in intuitionistic type theory. The type-theoretic component model uses composition instead.
- Implementation inheritance/reuse. The component model of intuitionistic type theory supports meta-programming, a very powerful mechanism for reuse. Though, implementation inheritance is not supported.
- Sum-of-product-of-function pattern. Intuitionistic type theory makes a distinction between functions and methods and between objects and components. A component is essentially a sum of methods.
Thus, in a sense, intuitionistic type theory with the component model can be seen as an object-oriented programming language with features 1, 2, 3, 4, 6, and, in a certain sense, 5, 8 and 9. In the explanations above, I speak of the component model of intuitionistic type theory. The concepts that form the basis of this component model are derived from an article by P. Hancock and A. Setzer (“Interactive programs in dependent type theory”, in Lect. Notes Comput. Sci., vol. 1862, pp. 317-331, 2000). The details on how these concepts make up a component model will be given in later post.
My conclusion is that many aspects of object-oriented programming are worth preserving. In particular I propose intuitionistic type theory as a way to unify functional programming, component based programming, metaprogramming (MDA), and logical verification. The aspect of object-oriented programming that has to give way is the notion of class. The notion of class unifies two notions I think ought to be kept separate, viz., the notion of record type (structure) and the notion of implementation of an interface, or collection of methods. The first notion is available in most programming languages, and, in particular, in intuitionistic type theory. For the second notion, the component model of intuitionistic type theory substitutes the notion of component.