How to create a parameterized infix macro in sweet.js - javascript

In the bluebird wiki article about JavaScript optimization killers, the author mentions that passing the arguments keyword to any function (except apply) will cause the parent function to not be optimizable. I would like to create a sweet.js macro that allows me to write standard idiomatic JavaScript but will take care of the optimization killer.
Ideally, I would like a macro that would take the following function:
function foo() {
var args = [].slice.call(arguments);
return args;
}
And output something like this:
function foo() {
var args = [];
for(var i, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
return args;
}
I am having trouble with getting the sweet.js macro syntax correct, however. This is what I have so far:
example.sjs
let arguments = macro {
rule infix {
[].slice.call |
} => {
[];
for(var i = 0, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
}
}
function toArray() {
var args = [].slice.call arguments
return args;
}
Which outputs the following:
function toArray() {
var args$2 = [];
for (var i = 0, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
return args$2;
}
I tried making my macro have parenthesis around the arguments keyword and also include the var declaration, but without any success. I tried something like this:
invalid macro
let arguments = macro {
rule infix {
var $var = [].slice.call ( | )
} => {
var $var = [];
for(var i = 0, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
}
}
This produces the following error:
SyntaxError: [syntaxCase] Infix macros require a `|` separator
414:
^

Right, so there are a couple of ways to do this. Putting arguments
inside of parens doesn't work because infix macros can't match outside
of enclosing delimiters so when the arguments macro gets invoked it
sees zero tokens before or after it (the error should be clearer).
Your other solution is running into hygiene problems since the
arguments macro needs access to the args identifier but infix
macros are not allowed to match before the equals sign when it's in a
var statement so it can't actually match the args identifier.
So couple of solutions. The easiest is to just do something like what
the bluebird wiki suggested:
macro argify {
rule { ( $arg ) } => {
var $arg;
for (var i, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
}
}
function foo() {
argify(args)
return args;
}
You could also go the unhygienic route (not really recommended but
arguments is already kinda unhygienic so…):
let function = macro {
case {$mname $name ( $parens ...) { $body ... } } => {
letstx $args = [makeIdent("args", #{$mname})];
return #{
function $name ( $parens ...) {
var $args = [];
for (var i, len = arguments.length; i < len; i++) {
$args.push(arguments[i]);
}
$body ...
}
}
}
}
function foo() {
return args;
}
Edit:
I just thought of another solution that would allow you to keep your current syntax by overriding var:
let var = macro {
rule { $args = [].slice.call(arguments) } => {
var $args = [];
for(var i, len = arguments.length; i < len; i++) {
$args.push(arguments[i]);
}
}
rule { $rest ... } => { var $rest ... }
}
function foo() {
var args = [].slice.call(arguments);
}

This isn't quite the same result, since it has a function wrapper (though it's invoked with apply), but it doesn't require you to override var and can be used in any expression position.
macro copy_args {
rule {} => {
function() {
var len = arguments.length;
var args = Array(len);
for (var i = 0; i < len; i++) {
args[i] = arguments[i];
}
return args;
}.apply(this, arguments)
}
}
let slice = macro {
rule infix { []. | .call(arguments) } => { copy_args }
rule infix { []. | .apply(arguments) } => { copy_args }
rule infix { Array.prototype. | .call(arguments) } => { copy_args }
rule infix { Array.prototype. | .apply(arguments) } => { copy_args }
rule { } => { slice }
}
function go() {
var args = Array.prototype.slice.call(arguments);
return args;
}
expands to
function go() {
var args = function () {
var len = arguments.length;
var args$2 = Array(len);
for (var i = 0; i < len; i++) {
args$2[i] = arguments[i];
}
return args$2;
}.apply(this, arguments);
return args;
}
Don't know if that would kill optimization though...

Related

Join rest of the elements of an array with the first element

I'm trying the following logic and not sure which array function can help. I'm not able to use map or es6 but would like to see an answer.
I tried the following:
/* JS */ - This is not working and would like to see how to make it work.
var input = ['x','y','z'];
var powerSetResult = powerSet(input);
console.log(powerSetResult);
/*Ouput should be [''.'x','y','z','xy,'xz','yz','xyz']*/
function powerSet(arr) {
var data = [];
for(var i=0; i<arr.length; i++) {
arr[i] = arr[i] + arr[i+1];
data.push(arr[i]);
}
console.log(data);
return data;
}
/* ES 6 */ - Is there a better way to do this?
let input = ['x','y','z'];
let powerSetResult = powerSet(input);
console.log(powerSetResult);
/*Ouput should be [''.'x','y','z','xy,'xz','yz','xyz']*/
const powerSet(arr) {
let data = arr.map(([s1, s2, s3]) => [``,`${s1}`,`${s2}`,`${s3}`,`${s1}${s2}`,`${s1}${s3}`,`${s2}${s3}`,`${s1}${s2}${s3}`]);
console.log(data);
return data;
}
Certainly not the most efficient... but you could do this:
var input = ['x','y','z'];
var powerSetResult = powerSet(input);
console.log(powerSetResult);
function powerSet(arr, result) {
result = result || new Set();
const str = arr.join('');
for (let i = 0; i < arr.length; i++) {
for (let j = i; j <= arr.length; j++) {
result.add(str.slice(i, j));
}
powerSet([...arr.slice(0, i), ...arr.slice(i+1)], result)
}
return Array.from(result).sort((a, b) => {
return (a.length - b.length) || a.localeCompare(b);
});
}
/* Output should be [''.'x','y','z','xy,'xz','yz','xyz'] */
Caveat being it will only work for single letter elements, but is easily modifiable to accommodate more.

JavaScript curry / schönfinkeln

Lets say I have the following functions declared
function curry(fn) {
var args = [];
// push everything but function itself into args
for (var i = 1; i < arguments.length; ++i) {
args.push(arguments[i]);
}
return function() {
var args2 = [];
for (var i = 0; i < arguments.length; i++) {
args2.push(arguments[i]);
}
var argstotal = args.concat(args2);
return fn.apply(argstotal);
};
}
function add(a,b){return a + b};
What im trying to do is obvious, I want to curry the add function which works great itself
var addCurry = curry(add);
addCurry(10, 20, 12314) // should deliver 30, gives NaN
Somehow it returns NaN and I dont know what Im doing wrong... Anybody got an idea?
You have an off-by-one error here: return fn.apply(argstotal)
The first argument to Function.prototype.apply is a scope, the value of this within the invocation, not the first argument. If you were to print out the arguments from within your curried function, you would see:
function curry(fn) {
var args = [];
// push everything but function itself into args
for (var i = 1; i < arguments.length; ++i) {
args.push(arguments[i]);
}
return function() {
var args2 = [];
for (var i = 0; i < arguments.length; i++) {
args2.push(arguments[i]);
}
var argstotal = args.concat(args2);
return fn.apply(argstotal);
};
}
function add(a, b) {
console.log('debug', this, arguments);
return a + b;
}
var cadd = curry(add, 1);
cadd(2);
You can very easily fix this in two ways:
Pass this through to the curried function
Ignore this and do not set it for curried functions
The first option is probably better, as it will surprise developers less. You can implement that using:
function curry(fn) {
var args = [];
// push everything but function itself into args
for (var i = 1; i < arguments.length; ++i) {
args.push(arguments[i]);
}
return function() {
var args2 = [];
for (var i = 0; i < arguments.length; i++) {
args2.push(arguments[i]);
}
var argstotal = args.concat(args2);
return fn.apply(this, argstotal); // <-- only change
};
}
function add(a, b) {
console.log('debug', /* this, */ arguments);
return a + b;
}
var cadd = curry(add, 1);
cadd(2);
The second option can be implemented with fn.apply(null, argstotal).

Cannot set property 'XY' of undefined

I have following code:
var favourites = JSON.parse(localStorage.getItem("favourites"));
Service.all().then(function (multiple) {
var array = [];
for (var i = 0; i < multiple.length; i++) {
for (var j = 0; j < favourites.length; j++) {
if (favourites[j].checked === true) {
if (multiple[i].Name === favourites[j].name) {
Service.getAllBySomething(multiple[i].Id).then(function (resources) {
var arrayOfSomething = [];
for (var k = 0; k < resources.length; k++) {
arrayOfSomething.push(resources[k].ResourceCategoryId);
}
arrayOfSomething = arrayOfSomething .filter(function (elem, pos, arr) {
return arr.indexOf(elem) == pos;
});
multiple[i].existingProperty= arrayOfSomething;
});
array.push(multiple[i]);
}
}
}
}
$scope.vendors = array;
});
My problem is that it says everytime 'Cannot set property existingProperty of undefined'. And I don't know why multiple[i] should be undefined at this line:
multiple[i].existingProperty= arrayOfSomething;
The property exists, I am sure. And it is defined, it is an empty array. And this empty array I want to replace with my made array in the loops.
Where is the fault? How I can fill the existingProperty with my array?
is Service.getAllBySomething asynchronous by any chance? Because in that case, by the time the callback function runs, i (captured in a closure only) has been moved to the end of the array.
Use an additional closure to capture the value of i at the time of dispatching the async call, like this:
Service.getAllBySomething(multiple[i].Id).then(function(i){
return function (resources) {
var arrayOfSomething = [];
for (var k = 0; k < resources.length; k++) {
arrayOfSomething.push(resources[k].ResourceCategoryId);
}
arrayOfSomething = arrayOfSomething .filter(function (elem, pos, arr) {
return arr.indexOf(elem) == pos;
});
multiple[i].existingProperty= arrayOfSomething;
};
}() );
Note the new function receiving i as a param and returning the function you previously used. I'm invoking immediately (an IIFE) to return the function to pass as callback. Inside that function the value of i will be the same as when you dispatch this async call, as it was copied when used as a parameter.

How do I properly pass arguments more than once in a javascript object literal?

I have an object literal similar to this:
var test = {
myFunc1: function () {
for (var i = 0, j = arguments.length; i < j; i++) {
alert('myFunc1: ' + arguments[i]);
}
},
myFunc2: function () {
for (var i = 0, j = arguments.length; i < j; i++) {
alert('myFunc2: ' + arguments[i]);
}
this.myFunc1.apply(this.myFunc2, arguments);
},
myFunc3: function () {
for (var i = 0, j = arguments.length; i < j; i++) {
alert('myFunc3: ' + arguments[i]);
}
this.myFunc2.apply(this.myFunc3, arguments);
}
}
When I call "myFunc3" I want it to call "MyFunc2" with all the arguments passed to "myFunc3". This is working as expected.
When I call "myFunc3" I want it to call "myFunc2" with all passed arguments, and I want myFunc1 to be called with all passed arguments that were originally passed to "myFunc3".
test.myFunc3('a', 'b');
Currently when I call this, I get 4 alert boxes, 2 for myFunc3 and 2 for myFunc2. Then I get an error:
Uncaught TypeError: Cannot call method 'apply' of undefined
If I need to pass a set of arguments more than one level deep in an object how can I do this? Is there a better more correct way to do this?
Here is a jsFiddle to demonstrate.
http://jsfiddle.net/taggedzi/fmPAQ/2/
Thanks in advance. Pure javascript only, no jquery please. Needs to work cross browser and platform.
That's because the this in myFunc2 is not anymore test but this.myFunc3. This was provided by your myFunc3 when calling this.myFunc2.apply(this.myFunc3, arguments);.
To visualize, your myFunc2 is doing:
(this.myFunc3).myFunc1(...);
//instead of
(this).myFunc1(...);
You should pass in only this so you are using the same this for the other functions.
this.myFunc2.apply(this, arguments);
Try this: it works. You should use this instead of this.FuncX
var test = {
myFunc1: function () {
for (var i = 0, j = arguments.length; i < j; i++) {
alert('myFunc1: ' + arguments[i]);
}
},
myFunc2: function () {
for (var i = 0, j = arguments.length; i < j; i++) {
alert('myFunc2: ' + arguments[i]);
}
this.myFunc1.apply(this, arguments);
},
myFunc3: function () {
for (var i = 0, j = arguments.length; i < j; i++) {
alert('myFunc3: ' + arguments[i]);
}
this.myFunc2.apply(this, arguments);
}
};
test.myFunc3('a', 'b');

Building an object from array in javascript

What I have is an array like this: ['foo','bar'] and I want to turn it into an object that looks like this:
{
foo:{
bar:{
etc:{}
}
}
}
I've tried with two loops but I can get it to work if there is three values in the array.
var obj = {};
var pointer = obj;
array.forEach(function (item) {
pointer = pointer[item] = {};
});
Here's the fiddle: http://jsfiddle.net/h67ts/
If you have to support IE < 9, you can either use a regular loop, or use this polyfill:
if ( !Array.prototype.forEach ) {
Array.prototype.forEach = function(fn, scope) {
for(var i = 0, len = this.length; i < len; ++i) {
fn.call(scope, this[i], i, this);
}
}
}

Categories