Variable assignment shortcut

Eval is limited in its power plus the only time I found `this` variable useful.

I often have the following code at the end of my functional pipelines

1
2
3
4
5
6
7
8
9
function doSomething() {
var items;
ajaxGet(url)
.then(checkDataSchema)
.then(extractItems)
.then(function assign(list) {
items = list;
});
}

The last function assign does nothing but assigns the returned list to a local variable items. The new ES6 syntax help a little, but still requires a functional expression

1
2
3
4
5
6
7
function doSomething() {
var items;
ajaxGet(url)
.then(checkDataSchema)
.then(extractItems)
.then((list) => items = list);
}

Can we eliminate the functional expression and the argument name list? I would love to be able to use something like this (function assign is curried)

1
2
3
4
5
6
7
8
var assign(variableName, value) { ... }
function doSomething() {
var items;
ajaxGet(url)
.then(checkDataSchema)
.then(extractItems)
.then(assign('items'));
}

Because function assign declares 2 arguments (target variable name and value), and is curried, the expression assign('items') returns a function that only expects value to complete its work.

Can we write this function? It is a dynamic expression, thus using eval is necessary. A first implementation certainly is simple enough.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var R = require('ramda');
function doSomething() {
var items;
var assign = R.curry(function (variableName, value) {
return eval(variableName + ' = ' + JSON.stringify(value));
});

ajaxGet()
.then(checkDataSchema)
.then(extractItems)
.then(assign('items'))
.then(function () {
console.log('items =', items);
});
}
doSomething();
// prints items = [ 'foo', 'bar' ]

We have the function assign right inside our scope with the variable items. When the eval executes, it uses the scope around it to evaluate variable references, thus it finds the correct items variable. This is unlike evaluating the code dynamically using new Function(code) approach, which only evaluates in the global scope.

Ok, this is working, I would like to reuse assign, maybe even mix it into the Ramda library. This is where we run into troubles

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var R = require('ramda');
var assign = R.curry(function (variableName, value) {
return eval(variableName + ' = ' + JSON.stringify(value)); // 1
});
function doSomething() {
var items;
ajaxGet()
.then(checkDataSchema)
.then(extractItems)
.then(assign('items'))
.then(function () {
console.log('items =', items);
});
}
doSomething();
// prints items = undefined

The evaluated assignment expression marked // 1 is no longer written in the same scope as the variable items. The JavaScript language despite its dynamic nature and flexibility only uses lexical scope - the source around the place where the function is written, not where it is evaluated. There is no way to force eval to use the runtime or call-site scope. That would be amazing, but would break closures and make the evaluation impossible to predict.

Can we find a work around? Our problem is that we are trying to overwrite the variable reference, not its value. The only way we could this is by passing and object and overwriting a property inside of it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var R = require('ramda');
var assign = R.curry(function (object, property, value) {
object[property] = value;
return object[property];
});
function doSomething() {
var result = {};
ajaxGet()
.then(checkDataSchema)
.then(extractItems)
.then(assign(result, 'items'))
.then(function () {
console.log('items =', result.items);
});
}
doSomething();
// prints items = [ 'foo', 'bar' ]

This works again. What if we don't want to use a function from a large library to do currying? Well, we could use plain Function.prototype.bind to do partial application in JavaScript.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function assign(object, property, value) {
object[property] = value;
return object[property];
}
function doSomething() {
var result = {};
ajaxGet()
.then(checkDataSchema)
.then(extractItems)
.then(assign.bind(null, result, 'items'))
.then(function () {
console.log('items =', result.items);
});
}
doSomething();
// prints items = [ 'foo', 'bar' ]

Short and sweet, but we can shorten this even more (by exactly 18 characters). Notice that the function assign puts the value into a passed object, while assign.bind sets the context of the assign. We can write instead the following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function assign(property, value) {
this[property] = value;
return this[property];
}
function doSomething() {
var result = {};
ajaxGet()
.then(checkDataSchema)
.then(extractItems)
.then(assign.bind(result, 'items'))
.then(function () {
console.log('items =', result.items);
});
}
doSomething();
// prints items = [ 'foo', 'bar' ]

I believe this is the only time I found using the keyword this helpful.

Here is a practical application: grabbing something in the unit tests. For example, it gets tiresome to write beforeEach callback just to assign a value to a variable

1
2
3
4
5
6
7
8
describe('a mocha spec', function () {
beforeEach(function () {
this.foo = 'foo';
});
it('is foo', function () {
console.assert(this.foo === 'foo');
});
});

If we have the assign from above we can make a shortcut

1
2
3
4
5
6
7
describe('assign in a mocha spec', function () {
// this.ctx in the describe callback points at the context for each unit test
beforeEach(assign.bind(this.ctx, 'foo', 'foo'));
it('is foo', function () {
console.assert(this.foo === 'foo');
});
});

Typically, the shortcut is more complicated and would be a promise or a function call, something like

1
2
3
4
5
6
function assign(property, value) {
return Promise.resolve(value).then(function (resolved) {
this[property] = resolved;
return this[property];
});
}

This assignment can handle the async unit test setup.