8 ms·
An object is in some ways a function that defines its own parameters (fields and methods) and arguments (state). This then is injected into another such functio
by ipnon 6y ago
An object is in some ways a function that defines its own parameters (fields and methods) and arguments (state). This then is injected into another such function.
- uryga 6y agoif 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
- hnick 6y agoIn perl an object is literally a data structure with funtions glued on. Which is kind of the opposite of what you say! Usually this is a hash (dictionary) which is similar to the object with fields that we all know, but it can just as easily be an array or scalar value.