Block scoped prototype extensions

# Boris Cherny (7 years ago)

Hey guys,

What would it take to get block scoped prototype extensions into JavaScript? I’m curious to get some thoughts before I write a proposal.

The use case is similar to Scala’s block scoped implicits. In my application code I want Array.prototype.indexOf to return an Option<number>, rather than number | -1. If I patch Array’s prototype directly, it will break other libraries in my project. It would be nice to have a way to extend the prototype for just a block, or just a file.

Would a combination of block-scoped imports (maybe dynamic imports, to patch the prototype) and some sort of onExitBlock hook (to unpatch the prototype) be enough to implement this? Has anyone else thought about this sort of feature?

Boris

# kdex (7 years ago)

One way to solve this might currently include extending Array and overridingindexOf in a derived class to reflect the Option behavior you're after.

# Boris Cherny (7 years ago)

I tried that approach, but it doesn’t work when creating objects via literal notation ([], {}). A bit clumsy to have to write “new Array(1,2,3)", or “Array(1,2,3)” every time.

# Logan Smyth (7 years ago)

This seems like a difficult thing to do because the prototype chain is only objects, so mutating them for the context of a single scope isn't easily done. A few cases come to mind as extremely confusing:

  • If you override the prototype in a scope then pass the object to another function inside the block, does it stay overridden when passed? What if you want to pass it as a normal array? Does that mean it needs to change the prototype back to default when calling the function, then change it back to your override again after?
  • What if something captures a reference to the object and then the block ends? Does every usage of that object in the captured scope then need to re-mutate the object then change it back again? If not, this means using this behavior in callbacks and such isn't possible.

This approach encourages a huge amount of mutation, and it seems like it would be incredibly confusing for average users to have objects changing behavior out from under them.

# kdex (7 years ago)

Overriding literals with a derived class is an entirely different problem. :)

# kdex (7 years ago)

… which I would be very happy to discuss, nonetheless. It's indeed a little painful to work with derived built-ins if you can't use literals.

Could we maybe have a syntax that, for its current block, declares how to interpret literals? Maybe something along the lines of:

class MyArray extends Array {};
Array as MyArray;
const array = [1, 2, 3];
assert(array instanceof MyArray);
# Michael Haufe (7 years ago)
# Augusto Moura (7 years ago)

You can wrap into a util function like that:

function customIndexOf() {
  return 'quack'
}

function withCustomIndexOf(fun) {
  const oldIndexOf = Array.prototype.indexOf
  Array.prototype.indexOf = customIndexOf
  fun()
  Array.prototype.indexOf = oldIndexOf
}

withCustomIndexOf(() => {
  console.log([123].indexOf()) // quack
})

console.log([123].indexOf()) // -1

Note, it will only work with sync code, async code (callbacks, promises) will ~probably~ be called after the reset of the prototype function

Edit: The problem with this solution is because it will actually change behavior down to the call stack, third party functions will have the behavior compromised

withCustomIndexOf(() => {
  $('*').find('div') // may not work because will execute with the `customIndexOf` in Array.prototype instead of the default method
})
# T.J. Crowder (7 years ago)

On Wed, Jul 5, 2017 at 7:10 PM, Boris Cherny <boris at performancejs.com> wrote:

The use case is similar to Scala’s block scoped implicits. In my application code I want Array.prototype.indexOf to return an Option<number>, rather than number | -1.

Why does it have to be called indexOf? Why not fooIndexOf, where foo is a short app-specific prefix? Changing the meaning of indexOf seems like changing black to white and running the risk of getting killed at the next zebra crossing.

On Wed, Jul 5, 2017 at 7:42 PM, Boris Cherny <boris at performancejs.com> wrote:

I tried that approach, but it doesn’t work when creating objects via literal notation ([], {}). A bit clumsy to have to write “new Array(1,2,3)", or “Array(1,2,3)” every time.

Surely not too bad if you provide yourself a short array-creation wrapper, like a?

-- T.J. Crowder

# Erik Arvidsson (7 years ago)

We tried to do this in the early Harmony era. We never managed to get this to work without unacceptable performance and semantic issues. If you dig around the archives looking for scoped object extensions or method extensions you can see the discussion that was had.

It seems like wiki.ecmascript.org is no longer available. The proposal was up there. Maybe someone has a mirror of it somewhere?

Erik

# Jordan Harband (7 years ago)