Jonah Jonah - 3 months ago 9
Javascript Question

Implementing Ruby Refinements in Javascript

Ruby's refinements allow you to temporarily "upgrade" an object within a lexical scope. I'm trying to implement a similar idea in javascript. Here's some working code that does almost what I want:

function dateExtension() {
return {
day: function() { return this.getDate() }
}
}

function refine(ctor, mixin) {
return function() {
var ret = new (Function.prototype.bind.apply(ctor, arguments));
return Object.assign(ret, mixin);
}
}

function test() {
// Cant overwrite native Date function,
// so have to rename it.
var XDate = refine(Date, dateExtension());
var d = new XDate();
console.log(d.day()); // prints the day of the month to console

}

test();


What I really want to do is this:

function test() {
var Date = refine(Date, dateExtension());
var d = new Date();
console.log(d.day()); // Uncaught TypeError: Bind must be called on a function
}


The idea would be to make the local
var Date
overide the built in
Date
, within the body of
test()
only. So that, within
test()
, it would acquire the new method
day()
, but outside of
test()
Date
would be unaffected. This apparently is not allowed.

Is there some workaround to make this idea work?

Answer

I made a tiny library called chill-patch to do exactly that. It enables you to use functions as methods, and to safely patch prototypes.

Here's an example of how to use it:

const chillPatch = require('chill-patch')
const lastFunc = arr => arr[arr.length - 1]
const array = [1, 2, 3]

// safely add a method to `Array` 
const last = chillPatch(Array, lastFunc, 'last')

// call the new method! 
array[last]() //=> 3 

If you want to roll your own, the entire source code is as follows:

'use strict'

const chillPatch = (klass, func, optionalDescription) => {
  const symbol = Symbol(optionalDescription)
  klass.prototype[symbol] = function(){
    const args = Array.prototype.slice.call(arguments)
    args.unshift(this)
    return func.apply(null, args)
  }
  return symbol
};

module.exports = chillPatch

I see you said in a comment that you do not want to modify prototypes, but your reason is probably that modifying prototypes is dangerous. However, the method above patches using Symbol, which is completely safe. The change will be invisible to other parts of the codebase unless someone is doing reflection with Object.getOwnPropertySymbols()