5 ms·
if it's a function, what are the inputs/outputs? do you mean that in the way of "an object is a poor person's closure"? in case anyone's unfamiliar, here's one
by uryga 6y ago
if it's a function, what are the inputs/outputs? do you mean that in the way of "an object is a poor person's closure"?
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:
// might be easier to first look at
// the usage example at the bottom
let Point = (x, y) => {
let self = (message, args=null) => {
switch (message) {
// getters
case 'x': return x;
case 'y': return y;
// some operations
// (immutable, but that's not required)
case 'toString':
return `Point(x=${x}, y=${y})`;
case 'move':
let [dx, dy] = args;
// use our getters
return Point(self('x')+dx, self('y')+dy);
// let's get DRY!
case 'plus':
let [other] = args;
return self('move', other('x'), other('y'));
default:
throw Error(`unknown message: ${message} ${JSON.stringify(args)}`);
}
};
return self;
};
let p1 = Point(3, 5);
// sending messages
p1('x') === 3;
p1('y') === 5;
p1('move', [1, 2])('toString');
// --> "Point(x=4, y=7)"
let p2 = Point(1, 2);
p1('plus', [p2])('toString');
// --> "Point(x=4, y=7)"
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.
- pbourke 6y agoThis is a neat example, but wouldn't dynamic dispatch imply polymorphism?
- josephg 6y agoIt’s dynamic dispatch because the decision about what code to call happens at runtime (via the switch statement) rather than compile time.
- uryga 6y agothis is particularly visible in the implementation of `plus` – `other` might not be a Point at all, so `other('x')` and `other('y')` might do anything; which i'd say is textbook (ad-hoc) polymorphism. i remember reading this somewhere: "branching on values is the ultimate dynamic dispatch" :)
- pbourke 6y agoBut that's essentially a table lookup, not a vtable lookup, right? 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.
- lalaithion 6y agolet 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; };
- uryga 6y agomaybe "extremely late binding" would be more appropriate? it's true that usually "dynamic dispatch" means a vtable, but i think this is dynamic dispatch in spirit - in fact "dispatching" messages to implementations is pretty much the only thing a closure-object does :) i chose to write the impls inline, so they're p much impossible to reuse, but that's not required. > 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 :)
- uryga 6y agoalso, i just made a possibly interesting connection with JS methods (and their weirdness). in the above implementation, a message is first class, so you can send the same message to multiple objects: 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)
- ipnon 6y agoExcellent example, thank you. 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.
- uryga 6y agothank you :) i hope i didn't hijack the thread! but i just love sharing stuff like this > 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
- ipnon 6y agoThe other interpretation has slipped my mind since yesterday, sorry mate. I do think it's an interesting idea that I now see I have no formal understanding of. Cheers.
- uryga 6y agoshame! cheers :) 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.
- btilly 6y agodo you mean that in the way of "an object is a poor person's closure"? Objects are a poor man's closures. And closures are a poor man's objects. Modern languages have both. And they serve different purposes.
- uryga 6y agoi intended to put "(and vice versa)" in a footnote, but forgot about it! you can see a relic of that in the last paragraph. 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)
- uryga 6y agobtw this could (sorta) be considered a special case of the fact that a tuple/struct (product type) can be represented as a function: 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