Objects are a poor man's closures. And closures are a poor man's objects.
Modern languages have both. And they serve different purposes.
though i must say, i'm pretty happy in languages that have closures but don't have objects, as long as there's a nice way to do ad-hoc polymorphism (like traits/typeclasses)
let moveUp = ['move', [0, 1]];
p1(...moveUp)
p2(...moveUp)
but you can't get a `move` that's "bound" to a particular object¹ – the "receiver" is decided when you "send" the message. which reminds me of how JS methods work: `this` is bound to what's "to the left of the dot" when you call a method, so unlike Python, obj.foo(1)
is not the same as let f = obj.foo
f(1) // Error: 'this' is undefined
maybe there's a connection to some language that inspired JS' OO model, with prototypes and all that?---
¹ well, unless you explicitly wrap it in another closure like
(args) => p1('move', args) let pair = (a, b) => (
(ix) => (
ix === 0 ? a :
ix === 1 ? b :
undefined
)
);
let p = pair(3, "foo");
p(0) // 3
p(1) // "foo"
i've seen sth like this used as the definition of tuples in a math context. (there are also other ways to do it too, like church/scott encoding)however the object has the extra wrinkle of self-referentiality, because the `self` function is defined recursively
Dynamic dispatch implies that you dispatch based on the class of the receiver. For instance, you can't have a Point3 value that uses Point's implementation of 'x' and its own implementation of 'toString' under this example.
let Point3 = (x, y, z) => {
let parent = Point(x, y);
let self = (message, args=null) => {
switch (message) {
// getters
case 'z': return z
// some operations
// (immutable, but that's not required)
case 'toString':
return `Point(x=${x}, y=${y}, z=${z})`;
case 'move':
let [dx, dy, dz] = args;
// use our getters
return Point(self('x')+dx, self('y')+dy, self('z')+dz);
// let's get DRY!
case 'plus':
let [other] = args;
return self('move', other('x'), other('y'), other('z'));
default:
parent(msg, args)
}
};
return self;
};> you can't have a Point3 value that uses Point's implementation of 'x' and its own implementation of 'toString' under this example.
you can't do much of anything under this this example! i didn't think a whole object system would fit in a HN comment, i intended it to be minimal :)
i remember reading this somewhere: "branching on values is the ultimate dynamic dispatch" :)
Edit: To answer your question, the set of all public methods could be seen as 1, but not the only, interpretation of the functional interface.
> the set of all public methods could be seen as 1, but not the only, interpretation of the functional interface.
could you describe another interpretation? "the set of public methods"¹ is the only thing i can think of when i hear about an object's "functional interface". it could be a terminology issue though – i'm reading "functional interface" in a general sort of way, but i could imagine it having some specific definition in OO theory.
---
¹ or recognized messages, in a more smalltalk-ish context
PS. if you're interested in weird perspectives on this, you might find Coinduction (and "codata") interesting. from wikipedia:
> Informally, rather than defining a function by pattern-matching on each of the inductive constructors, one defines each of the "destructors" or "observers" over the function result.
it's a deep rabbit hole, but i remember it being applied OOP-ish things – "destructors" would roughly correspond to exposed methods.
in case anyone's unfamiliar, here's one basic way to emulate objects with closures. i think i saw it in SICP (?), reproducing it here because i just love how simple it is:
dynamic dispatch & message passing, just like that! and you can easily do `__getattr__/method_missing`-style dynamicism just by looking at `message`.for the other way around, see how Java lambdas desugar to objects with a "call" method and closed-over variables as members.