0

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?

2
  • You can downgrade the object manually when parting with it, but otherwise I'd say what you're trying to do is impossible. Commented Jan 26, 2016 at 15:23
  • yeah I should have mentioned that I didn't want to patch and unpatch the prototype chain Commented Jan 26, 2016 at 15:36

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

3 Comments

Interesting, so last is accessible only inside the module? (barring reflection inquiries)
@Jonah close. last is just a variable that gives you a reference to a symbol. So if you define last at the highest scope of the module, that's the scope for last. That said, you can pass the reference across modules, just as you can with any other JS reference. For example, you could do module.exports.last = last, in which case anyone who requires your module can use last as well. I can see this getting confusing, so can't wholeheartedly recommend using it. It's cool, though.
nah, it's not confusing, makes perfect sense. thanks for the answer.
0

So I came up with something that works, although it's fairly hacky.

Here's a fiddle: https://jsfiddle.net/40cty4qa/

And here's the code

function lg() { console.log.apply(console, arguments) }

function extend(o, mixin) {
  for (var k in mixin) o.prototype[k] = mixin[k]
}
function unextend(o, mixin) {
  for (var k in mixin) delete o.prototype[k];
}

Function.prototype.refine = function(ctor, mixin) {
  var self = this;
  return function() {
    extend(ctor, mixin);
    var ret = self.apply(this, arguments);
    unextend(ctor, mixin);
    return ret;
  }
}

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() {
  var d = new Date();
  lg(d.day());
}
test = test.refine(Date, dateExtension());

test();  // This works, since were inside a refinement
var d = new Date();
lg(d.day());  // This doesnt work, as desired.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.