Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.



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.


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)


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)


p1.move.bind(p1)


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.


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


This is a neat example, but wouldn't dynamic dispatch imply polymorphism?


It’s dynamic dispatch because the decision about what code to call happens at runtime (via the switch statement) rather than compile time.


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.


  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;
  };


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 :)


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" :)


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.


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


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.


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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: