News for hackers. Stuff that matters.
Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @05:52PM (#23197933)
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.

Re:Pretending OOP Never Happened by btilly on Friday May 15, 2020 @08:45PM (#23199309)
do 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.

Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @09:12PM (#23199491)
i 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)

Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @08:06PM (#23199008)
also, 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)
Re:Pretending OOP Never Happened by krallja on Friday May 15, 2020 @08:46PM (#23199321)
p1.move.bind(p1)
Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @09:29PM (#23199596)
i know :) the point is that that's not the default (unlike many other languages), and i was wondering about a possible origin for that choice.
Re:Pretending OOP Never Happened by uryga on Saturday May 16, 2020 @08:25AM (#23202831)
btw 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

Re:Pretending OOP Never Happened by pbourke on Friday May 15, 2020 @06:45PM (#23198400)
This is a neat example, but wouldn't dynamic dispatch imply polymorphism?
Re:Pretending OOP Never Happened by josephg on Friday May 15, 2020 @06:52PM (#23198453)
It’s dynamic dispatch because the decision about what code to call happens at runtime (via the switch statement) rather than compile time.
Re:Pretending OOP Never Happened by pbourke on Friday May 15, 2020 @08:40PM (#23199272)
But 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.

Re:Pretending OOP Never Happened by lalaithion on Friday May 15, 2020 @10:42PM (#23200021)

  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;
  };
Re:Pretending OOP Never Happened by uryga on Saturday May 16, 2020 @08:08AM (#23202743)
maybe "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 :)

Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @08:14PM (#23199063)
this 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" :)

Re:Pretending OOP Never Happened by ipnon on Friday May 15, 2020 @08:22PM (#23199118)
Excellent 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.

Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @10:02PM (#23199815)
thank 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

Re:Pretending OOP Never Happened by ipnon on Saturday May 16, 2020 @12:43PM (#23204979)
The 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.
Re:Pretending OOP Never Happened by uryga on Saturday May 16, 2020 @01:03PM (#23205149)
shame! 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.