Skip to content

Instantly share code, notes, and snippets.

@polotek
Created May 5, 2012 00:50
Show Gist options
  • Save polotek/2598833 to your computer and use it in GitHub Desktop.
Save polotek/2598833 to your computer and use it in GitHub Desktop.
Some thoughts on inheritance and capability checking in js.

I promised some people I would write a little about my ideas on inheritance in js. This is a code snippet from my procstreams lib. https://github.com/polotek/procstreams/blob/chain-state/procstreams.js#L132-145

// this is a process object
if((cmd === process || typeof cmd.spawn == 'function')) {
  // this is already a procstream
  if(procStream.is(cmd)) {
    cmd.on('exit', callback);
    return cmd;
  } else {
  // this is a process that needs to be enhanced
    proc = procStream.enhance(cmd, stateFunc);
  }
} else {
  proc = procStream.create(cmd, args, opts, stateFunc);

  proc._args = o;
}

There are a few things happening here. I've long been in the camp of people saying instanceof is a broken construct in js. There's no such thing as a static class that doesn't change, so it's useless to think of objects as being an "instance" of something. The IS A concept from traditional OO just doesn't hold up. Plus it goes against the idiom that js is flexible, and that monkey-patching objects is not only acceptable but encouraged.

Since that thing you get may not be the exact type of thing you expected, what you really want to do is find out if it has the capabilities you expect. Or put another way, you want to know if it follows the public api you expect to use. This is the concept of capability checking or duck-typing that js people are becoming familiar with. It's the same way we decide what features a browser environment has. Just ask objects about their properties.

But we want to do this in a more generic sense for objects that come from other modules. Above I'm getting an object called cmd and I need to create a procstream object based of that. So I'm inspecting cmd to see what it is. Because of the way my api works, cmd may already be a procstream. I want a nice way to check that, but instanceof won't cut it here for various reasons.

So I introduce this idea of is(). It allows the constructor for a "class" of objects to tell me if this object belongs. It takes in an object and checks its capabilities. If it passes, I get true, otherwise false. Here's what it looks like.

procStream.is = function(proc) {
  if(proc) {
    return typeof proc.spawn == 'function' && proc.pipe == 'function';
  }
  return false;
}

A procstream actually has the capabilities of a node child_process and a node stream. I do a simple check for the primary features of these 2 things. Notice that capability checking doesn't have to be super thorough. These aren't the only 2 functions I might call on this object. You just have to find a combination that is unique enough that you're almost certainly right. If an object has these 2 functions and it's not a procstream, I've almost certainly lost complete control over my environment :)

Constructor.is(obj) is not a perfect api for this. The ordering and name don't intuitively imply what it's doing. But it's short and sweet and nothing else seemed better. I'm open to better ideas.

The other thing that makes this snippet interesting is how I'm creating the procstreams. A procstream "inherits" all the behavior of a node child_process. But I can't actually override the creation of child_processes (because node doesn't expose the construction/prototype)[]. So I had to take a different route. Instead I have to get the child_process and "enhance" it with my own functionality. The procstream is actually the same object as the child process, it just has my own methods. You can take a look at the protochains module to see how enhance and create work. And then @izs's inherits module for the constructor stuff.

I wanna give a hat tip to @creationix. I remember him experimenting with various ways of creating objects a while back.

I'm still not sure about the drawbacks of this approach. But it works well given my constraints on this project. I'm always open to feedback.

@felixge
Copy link

felixge commented May 5, 2012

monkey-patching objects is not only acceptable but encouraged

With great power comes great responsibility. Monkey patching something is always a last resort for me. It usually indicates problems in my design or the libs I'm using.

As for the rest of this: I'm cool with duck typing, and yes instanceof is useless for that. But saying that it's a broken construct in js is a bit overkill, it just isn't a good tool for all problems.

@polotek
Copy link
Author

polotek commented May 5, 2012

No it's broken. And by that I mean it promises to do something that it cannot actually do. If someone modifies the prototype of an object or modifies the prototype of the constructor that created it, then instanceof will lie to you. That's the definition of broken to me. You can call it what you like. And it's not really helpful to try to sugar coat it. This is my opinion and people can take it or leave it. But it's also a recommendation I'm making to anyone who may want to listen, so I'm not equivocating on it. There are ways to do what we want in a more consistent and at least predictable manner. The only exception I can see a case for is using instanceof to check whether a constructor was correctly called with new. But I'm kind of leaning towards avoiding that as well with "create" type apis.

@lesliepearson
Copy link

I had to look up duck-typing, but I think it makes sense that in order to check something is what we want it to be, we check for the properties that would make it a "duck". It was only in reading this that I realize I haven't used instanceOf in js.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment