Language extremists from the far right are spreading misinformation online about the essence of object-oriented programming (OOP). They say things like ‘… a language feature would not be worthy of the name “class” without supporting inheritance …’ insinuating that inheritance is an essential feature of OOP. The ministry of truth feels obliged to set the record straight here.
The essence of OOP is clearly objects.
But philosophically, an object is ‘a thing observed’.
This particularly means that an object is determined by its properties because, in general, this is ultimately all one can observe about a thing.
In other words, an object is something that is defined intensionally.
Hence, the essence of OOP is things defined intensionally.
Given that characterization of OOP, it makes sense to say that a programming language is object-oriented if and only if its essential language constructs model intensional definitions.
And, as algebraic data types with pattern matching are currently the best choice for extensional definitions, algebraic codata types with copattern matching [PDF] are currently the best choice for intensional ones.
So, regarding object-oriented programming languages, codata is the way to go.
A codata type is much like an interface as you might know it.
And a copattern match becomes an object by procedurally abstracting ambient values, where the abstraction can be observed by evaluating the branches.
The lambda-lifted copattern match is then the class of the corresponding object.
A leisurely introduction to codata is Codata in Action [PDF].
As you can see, classes do not need a notion of inheritance to deserve their name.
After all, this is not surprising, as inheritance is rather a code-sharing mechanism on top of objects inspired by biological inheritance.
Note that, as such, it is very powerful, and language designers should take care when introducing it into their languages.
In particular, installing it without restrictions allows one to break the encapsulation that arises from the intensional nature of objects.
In general, however, adding a feature to a language that changes the properties of others should not be done without justification.
Additional Information for Advanced Truth Seekers
Readers with some knowledge of academic programming language research might wonder how this relates to the two prevalent basic models of OOP in type theory.
In Simple Type-Theoretic Foundations For Object-Oriented Programming [PDF], a model of OOP is introduced based on type abstraction with existential types:
\[\begin{align*} \exists \texttt{Rep} .\, \texttt{Rep} \times \left( \texttt{Rep} \to \lbrace \ell_{1} \colon T_{1}[\texttt{Rep}], \ldots, \ell_{n} \colon T_{n}[\texttt{Rep}] \rbrace \right) \end{align*}\]This model simplifies the other famous model based on procedural abstraction with recursive record types:
\[\begin{align*} \mu \texttt{X} .\, \lbrace \ell_{1} \colon T_{1}[\texttt{X}], \ldots, \ell_{n} \colon T_{n}[\texttt{X}] \rbrace \end{align*}\](which is very close to codata, by the way). However, the simpler model based on type abstraction is strictly lower-level than the one based on procedural abstraction, in the same sense that a typed closure
\[\begin{align*} \exists \texttt{Env} .\, \texttt{Env} \times \left( \texttt{Env} \to T_{1} \to T_{2} \right) \end{align*}\]is strictly lower-level than an inhabitant of type $T_{1} \to T_{2}$, which procedurally abstracts ambient variables (e.g., an open lambda-abstraction).
Interestingly, this level change affects the validity of the autognostic principle (essential to OOP) [PDF]. The principle roughly states that an object cannot make assumptions about the internals of other objects (in accordance to what is said above). The existential type clearly violates this principle, as the methods have arbitrary access to the abstracted type.
However, there is a situation in which this is not the case. Namely, if the type abstraction $\texttt{X} \mapsto T_{1}[\texttt{X}] \times \cdots \times T_{n}[\texttt{X}]$ uses the abstracted parameter only in strictly positive positions, then the methods cannot exploit access to the abstracted type, because there is nothing to access.
The striking fact is that in this case the existential type is a Church-encoding [PDF] in System F of the greatest fixpoint defined by the recursive record type. This is precisely the coinductive type arising from that type abstraction, modeling the corresponding codata type.
Addendum
A notion related to OOP is type classes. This is because simple type classes behave very much like the existential type:
\[\begin{align*} \exists \texttt{Rep} .\, \texttt{Rep} \to \lbrace \ell_{1} \colon T_{1}[\texttt{Rep}], \ldots, \ell_{n} \colon T_{n}[\texttt{Rep}] \rbrace \end{align*}\]which closely resembles the existential types used to model OOP. This existential type generalizes the type of lambda-lifts:
\[\begin{align*} \exists \texttt{Env} .\, \texttt{Env} \to T_{1} \to T_{2} \end{align*}\]This suggests that one difference between OOP and type classes can be understood as a generalization of the difference between closures and lambda-lifts.