I understand the amusement value of this quote, but I never quite understood what is so poor about a tuple of functions closed over same data into a closure. Apart from inheritance, what does it lose. In fact its good that it loses inheritance. Syntax of many OOP languages makes it way too easy to use inheritance -- common things ought to be easy and potential design mistakes hard.
> but I never quite understood what is so poor about a tuple of functions closed over same data into a closure. Apart from inheritance, what does it lose.
In the closure-based version, getFullName and setFirstName are defined anew for each instance of Person, using more memory each time. Typically isn't an issue unless you're creating a massive number of them, but the class-based version doesn't have this problem.
Also, just to throw it in the mix as well, the class-based version I believe is largely just syntactic sugar over the traditional way of creating objects in javascript, using prototype-based methods:
An optimizing compiler could easily detect that a bunch of closures share an upvalue block in an enclosing "constructor" and then instead make that block explicit, and rewrite an entire constructor in a way that doesn't involve multiple closures at all (given that escape analysis and lang rules allow that). E.g.:
// optimized
function constructor(...) {
let this = {...}
function method(this, ...) {...}
return function (name, ...) {
switch (name) {
case 'method': return method(this, ...)
// or return a temp closure
// like it does in tfa
constructor()('method', 42)
And vice versa, on a computer that does closures in hardware better than objects for some unrealistic reason.
That's why both are just abstractions, whose semantics may better fit one or another implementation, but it really doesn't matter for maths.
You don't lose inheritance. This is especially clear in Python, e.g.
class Foo(Baz):
def foo(self, bar):
return self.baz(bar + 1)
Notice that we're not calling the 'baz' method via some fixed lexical variable; we're looking it up in the 'self' object, which has been passed in (implicitly) as an argument ('self' will also implicitly be passed into 'self.bar' as its first argument). Java's 'this' serves a similar role, except even more things are done implicitly.
One slight complication: 'self' should be lazy, so we can take the fixed point (so every method sees all of the overrides). This is easy in lazy languages (e.g. Nix); in strict languages we can just make 'self' a function: it looks up a given name in the object's properties/methods, and if not found it invokes the superclass (AKA 'super').