How do I run a function with arguments from an object? - javascript

I have a lot of functions like the following:
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
I have an object with I would like to use to fill in the arguments of the function
var obj = {liked: 'apple', disliked: 'pear'}
How can I run the function using the object to specify the arguments?
I tried using spread syntax:
runme(...obj)
But this produces:
TypeError: Found non-callable ##iterator
How can I run a function with parameters from an object?
I can't change the functions, as I am creating a wrapper that needs to be able to handle arbitrary functions.
Edit: I've edited the post to use 'liked' and 'disliked' instead of 'one' and 'two' as this better shows that ordering matters.
I can use any version of JavaScript, up to and including ES9.

There is no general-purpose way to accomplish this.
If you can guarantee the provenance of the source then you could use an AST operation to build your wrappers during a transpilation or load phase.
If you cannot, then you're particularly out of luck, because the parameter names may be mangled, making an object-key-to-parameter-name transformation impossible.

I can't change the functions, as I am creating a wrapper that needs to be able to handle arbitrary functions.
That's unfortunate, since making them accept a destructured parameter would be exactly what you need.
Unfortunately, the names of parameters are not available unless you parse the result of calling toString on the function, which is...fraught with peril. You basically need a full JavaScript parser (because parameter lists are complex these days, including possibly containing entire function definitions for default values) and minifiers and such may rename parameters (changing liked to _0, for instance).
You also can't count on the order of the properties in an object. (They do have an order, but not one that helps here...or almost anywhere else.)
You've said you need to handle functions whose parameters you don't know in advance, so my various ideas around wrapping functions with utilities that require passing in the names of the parameters won't work. (If anyone's curious, look at the revision list to see those.)
You can do this from toString if we can make several assumptions:
The function parameter lists are simple. They don't include destructuring or default values.
Comments are not used within the parameter lists.
Your minifier does not rename function parameters.
The functions are all traditional functions, methods, or arrow functions that do have () around the parameter list (so for instance, (x) => x * 2, not just x => x * 2).
You don't mind that it'll be fairly inefficient (parsing each time).
That's a lot of assumptions and I don't recommend it. But if you can rely on them:
// LOTS of assumptions here!
function run(f, obj) {
let params = /\(([\w\s,]*)\)/.exec(String(f));
if (!params) {
throw new Error("Couldn't parse function");
}
params = params[1].split(/\s*,\s*/).map(n => n.trim());
return f.apply(this, params.map(param => obj[param]));
}
run(runme, obj);
Live Example:
// Traditional function
const runme = function(liked, disliked){
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Traditional function with newlines
const runme2 = function(
liked,
disliked
){
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Arrow function
const runme3 = (liked, disliked) => {
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Method
const {runme4} = {
runme4(liked, disliked) {
console.log(`i like ${liked} but I hate ${disliked}`)
}
};
const obj = {liked: 'apple', disliked: 'pear'}
function run(f, obj) {
let params = /\(([\w\s,]*)\)/.exec(String(f));
if (!params) {
throw new Error("Couldn't parse function");
}
params = params[1].split(/\s*,\s*/).map(n => n.trim());
return f.apply(this, params.map(param => obj[param]));
}
run(runme, obj);
run(runme2, obj);
run(runme3, obj);
run(runme4, obj);
That works because Function.prototype.toString is standardized now, and even in resource-constrained environments it's required to include the parameter list (but may well not include the rest of the function implementation).

Answer Re-written to factor in correct ordering.
So long as your object keys are named to match the paramaters, you can parse the functions like the following: You can see that no matter which order they are passed in, the output is correct;
var obj = {liked: 'apple', disliked: 'pear'}
var runme = function (liked, disliked) {
console.log(`i like ${liked} but I dislike ${disliked}`)
}
var translateFunc = function (func, args) {
let funcAsString = func.toString();
let argNames = funcAsString.slice(funcAsString.indexOf('(') + 1, funcAsString.indexOf(')')).match(/([^\s,]+)/g);
let parsedArgs = [];
for (let a of argNames) {
for (let k of Object.keys(args)) {
if (k == a) {
parsedArgs.push(args[a]);
}
}
}
eval(func(...parsedArgs));
}
translateFunc(runme, obj);
obj = {disliked: 'pear', liked: 'apple'}
translateFunc(runme, obj);

A bit late, but here's another try. I think it works, but minification of the code will change your function arguments but not the object properties, so you'll need to work around that.
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
var obj = {liked: 'apple', disliked: 'pear'}
const wrap = runme => {
const regex = new RegExp(/\(([^\)]*)\)/)
const args = regex.exec(runme.toString())[1].split(',').map(s => s.trim())
return obj => {
const result = args.map(a => obj[a])
runme(...result)
}
}
const wrappedRunme = wrap(runme);
console.log(wrappedRunme(obj))

I got the same error and just in case this was the issue, here's what resolved it for me...
Instead of:
runme(...obj)
I needed to spread the object into a new object:
runme({ ...obj })

You can do that in following steps:
First convert the function to a string
Use RegExp to get the arguments of the function.
Use split() to convert the argument string to array of arguments.
Then use reduce() on that array and create a new ordered array having values of given object.
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
function wrapper(obj){
let args = runme.toString().match(/\(.+\)/)[0]
args = args.slice(1,-1);
args = args.split(',').map(x => x.trim());
let ordered = args.reduce((ac,ar) => [...ac,obj[ar]],[]);
runme(...ordered);
}
wrapper({liked:"liked", disliked:"disliked"})
wrapper({ disliked:"disliked",liked:"liked"})

Related

Converting function with logic and ui into higher order functions

I'm trying to improve my JavaScript skills. I'm learning composability and functional patterns and I'm totally lost.
I have two functions: one mapping an array and the other called from within the previous function to generate the markup.
const names = ['peter', 'paul', 'patrice']
const namesMarkup = name => {
return `<p>${name}</p>`
}
const showNames = listOfNames => {
return listOfNames.map(el => {
return namesMarkup(el)
})
}
showNames(names)
I have been reading about HOF, which technically are functions that take a function as an argument and/or return a function.
How could I compose these functions to have a HOF?
I went through the basic examples like
const square = num => num * num
const plus10 = (num, callback) => {
return callback(num) + 10
}
console.log(addTwo(7, square))
but I cannot make my mind around the previous example and working with lists.
I will appreciate help since the more I research the more confused I get.
Your mistake is to assume an array for showNames. Never do this. Always implement the simplest version of a function. In FP array is a computational effect. Don't implement such an effectful function as default:
const nameMarkup = name => {
return `<p>${name}</p>`;
}
const nameMarkup2 = name => {
return `<p>${name.toUpperCase()}!</p>`;
}
const showName = f => name => {
const r = f(name);
/* do something useful with r */
return r;
}
const names = ['peter', 'paul', 'patrice']
console.log(
showName(nameMarkup) ("peter"));
// lift the HOF if you want to process a non-deterministic number of names:
console.log(
names.map(showName(nameMarkup2)));
Now swapping the markup just means to pass another function argument. Your showName is more general, because a HOF lets you pass part of the functionality.
If we drop the array requirement, your showNames doesn't do anything useful anymore. It still illustrates the underlying idea, though.

Gremlin DSL usage errors within `repeat` step

We are using gremlin-javascript and have recently started to define a DSL to simplify our queries.
I am not sure if I've overlooked some caveat, but when attempting to use DSL methods within a repeat step, I consistently receive (...).someDslFunction is not a function errors, but using the same DSL function outside of repeat works without issue.
Here is a short (contrived) DSL definition that produces this issue:
class CustomDSLTraversal extends GraphTraversal {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode);
}
hasNotLabel(...args) {
return this.not(__.hasLabel(...args));
}
filterNotLabel(...args) {
return this.filter(__.hasNotLabel(...args));
}
}
class CustomDSLTraversalSource extends GraphTraversalSource {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode, CustomDSLTraversalSource, CustomDSLTraversal);
}
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
...gremlin.process.statics
};
const __ = statics;
const g = traversal(CustomDSLTraversalSource).withRemote(connection);
And here are two uses of it, the first works without issue, the second causes the __.outE().(...).filterNotLabel is not a function error.
g.V('foo').outE().filterNotLabel('x', 'y').otherV(); // No errors
g.V('foo').repeat(__.outE().filterNotLabel('x', 'y').otherV()).times(1); // Error
// __.outE(...).filterNotLabel is not a function
EDIT: Thanks #stephen for pointing out the now so obvious issue:
I had redefined callOnEmptyTraversal for use with our DSL, and foolishly destructured the standard TinkerPop anonymous traversals into our custom ones. These obviously are calling the original callOnEmptyTraversal which does indeed use an instance of the base GraphTraversal.
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
mapToObject: (...args) => callOnEmptyTraversal('mapToObject', args),
...gremlin.process.statics // Whoops
};
const __ = statics;
SOLUTION: Just in case anyone else runs into this scenario. This is how I solved the issue of merging our DSL anonymous traversal spawns with the standard TinkerPop ones:
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
function mapToCallOnEmptyTraversal(s, fn) {
s[fn] = (...args) => callOnEmptyTraversal(fn, args);
return s;
}
const statics = ['hasNotLabel', 'mapToObject']
.concat(Object.keys(gremlin.process.statics))
.reduce(mapToCallOnEmptyTraversal, {});
const __ = statics;
I assume that the problem is that it's because you start your traversal with __ which is the standard TinkerPop spawn for anonymous traversals. As a result you get a GraphTraversal created rather than your CustomDSLTraversalSource. The TinkerPop gremlin-javascript documentation states that:
steps that are made available on a GraphTraversal should also be made available as spawns for anonymous traversals
So you probably should have your own version of __ that returns the CustomDSLTraversalSource. If you want to see more explicitly where things are going wrong, see in the code that callOnEmptyTraversal() returns GraphTraversal and obviously your DSL methods won't be available on that class.

can I emulate a C-like array of pointers in javascript?

I'd like to be able to store the addresses of a bunch of different variables in an array. This allows me to access the variables by name or iterate through them if I need to. Is this possible in JS?
(function(ns){
ns.obj = new function(){
var foo = "foo";
var bar = "bar";
//i really want this:
//var ary = [&foo, &bar];
var ary = [foo, bar];
this.print = function() {
console.log( foo );
console.log( bar );
}
this.setFoo = function( newFoo ) {
//i really want this:
//*(ary[0]) = newFoo;
ary[0] = newFoo;
}
this.printAry = function() {
for( var i=0; i < ary.length; ++i ) {
console.log( ary[i] );
}
}
};
}(window.ns = window.ns || {}) );
ns.obj.print();
ns.obj.setFoo("newfoo!");
ns.obj.printAry();
ns.obj.print();
I looked at this:
JavaScript array of pointers like in C++
But I'd like to be able to use an element of ary on the LHS of an assignment and I don't think that example works in this situation.
WHY ON EARTH DO I WANT TO DO THIS?
A lot of comments so far have (rightfully) asked why I'd want to do this. I'm dealing with a proprietary API that involves an asynchronous object initialization mechanism. Basically I create an instance of an object and then pass it to this initializer to be able to actually use it. The initializer includes a field for an onSuccess handler to notify of successful initialization. My fully initialized object is passed as an argument into this success handler so that I can grab a reference to it.
I'm then free to initialize my next object. It looks kinda like this:
var a = new api.ApiObject();
var b = new api.ApiObject();
var c = new api.ApiObject();
var d = new api.ApiObject();
//omg this is ugly
api.initializeObject( {
objToInit: a,
onSuccess: function(args) {
a = args.obj;
api.initializeObject( {
objToInit: b,
onSuccess: function(args) {
b = args.obj;
api.initializeObject( {
objToInit: c,
onSuccess: function(args) {
c = args.obj;
api.initializeObject( {
objToInit: d,
onSuccess: function(args) {
d = args.obj;
}
} );
}
} );
}
} );
}
} );
a.doCoolStuff();
//and so on
This deeply nested mess just gets worse as I add more api.ApiObjects(). So what do I do to fix this? I can't change the API, but maybe a recursive function could help:
//maybe a recursive function could make this more concise?
function doInitialize( ary ) {
api.initializeObject( {
objToInit: ary[0];
onSuccess: function(args) {
//i'd like to assign this passed in reference to my local
//reference outside this function (var a, b, etc).
//An array of pointers would be useful here.
//how else can I get this assigned out, cuz this doesn't work...
ary[0] = args.obj;
if( ary.length > 1 ) {
ary.splice( 0, 1 );
doInitialize( ary );
}
}
}
}
doInitialize( [a,b,c,d] );
//this won't work because I don't have a reference to the fully initialized object
a.doCoolStuff();
So maybe the better question is: is there an established pattern to deal with asynchronous success chaining like this? I think I've seen other public JS frameworks (like dojo) use this sort of onSuccess chaining... how do I make this not ugly?
I might suggest that if your primary purpose for this is convenience as regards nesting of asynchronous callbacks, that you should consider a deferred/promise system.
I've written a couple of different promise libraries by hand.
jQuery comes with one built in (as do most "ajax libraries").
Here's what this might look like, in a better world:
doThingOne()
.then(doThingTwo)
.then(doThingThree)
.then(launch);
Assuming that doThingOne returns a promise.
A more familiar looking interface for people who use jQuery (or most other promise-using large libraries), might look like this:
var imageLoader = $.Deferred(),
loading = imageLoader.promise();
loading
.done(gallery.render.bind(gallery))
.done(gallery.show.bind(gallery));
var img = new Image(),
url = "...";
img.onload = function () { imageLoader.resolve(img); };
img.onerror = function () { imageLoader.reject("error message"); };
img.src = url;
Very basically, the Deferred above will hold two private arrays (one for "success", one for "failure"), and will extend an interface which allows the async part of the application to "succeed" or "fail", and will pass in whatever is chosen to be data/a callback/etc.
It also extends a promise method, which returns a promise object, containing subscription functions for the two private arrays. So you pass the promise object around to interested parties, and they subscribe callbacks to be iterated through, on success/failure of the async operation (and passed anything which is passed to the .resolve/.reject method of the operation).
This might seem like an inversion or extension of just adding a custom-event/listener/etc...
And it is.
The benefit of the abstraction is that the interface is cleaner.
Hiding this stuff inside of object interfaces, and just passing async promise-objects around can make your code look 100% synchronous:
var images = ImageLoader(),
gallery = ImageGallery(),
photo;
photo = images.load("//url.com/image.png"); // assuming `.load` returns a promise object
gallery.show(photo); // just a promise object, but internally,
//`.show` would subscribe a private method to the promise object
And doing things like having three separate async operations, which can arrive in any order, but must all be successful before advancing, then you can have something like this (again jQuery, but doing it by hand is possible, too).
$.when(promise_obj_1, promise_obj_2, promise_obj_3)
.done(nextPhase);
nextPhase, of course, being a callback which you anticipate to be fired if all three promises are successfully completed.
I'd be happy to provide implementation details for a barebones promise system, if you're like me, and don't like using different libraries without first understanding how each piece works on its own, and being able to replicate its functionality, without copying code.
The answer to the first part of your question is to use an object. You're thinking in C which doesn't have iteratable structs so C programmers reach for arrays. In JS objects are iteratable. So you should write it as:
ary = {
foo : 'foo',
bar : 'bar'
}
Or if we look at your second example:
var apis = {
a : new api.ApiObject(),
b : new api.ApiObject(),
c : new api.ApiObject(),
d : new api.ApiObject()
}
Now, as for the second part of your question. Your pseudo recursive code (pseudo because it's not really recursive in the stack sense since it's async) will now work with the apis object above. But you pass the keys instead of the object:
doInitialize( ['a','b','c','d'] );
Obviously, the bit above can be done dynamically by iterating through the apis object. Anyway, in the onSuccess part of the code you assign the result like this:
apis[ary[0]] = args.obj;
Oh, and obviously the objToInit should now be apis[ary[0]].
Now doing this should work as you expect:
apis.a.doCoolStuff();

How to chain functions without using prototype?

I have a bunch of useful functions that I have collected during my whole life.
function one(num){
return num+1;
}
function two(num){
return num+2;
}
I can call them with two(two(one(5)))
But I would prefer to use (5).one().two().two()
How can I achieve this without using prototype?
I tried to see how underscore chain works, but their code is too intense to understand it
The dot syntax is reserved for objects. So you can do something like
function MyNumber(n) {
var internal = Number(n);
this.one = function() {
internal += 1;
// here comes the magic that allows chaining:
return this;
}
// this.two analogous
this.valueOf = function() {
return internal;
}
}
new MyNumber(5).one().two().two().valueOf(); // 10
Or you're going to implement these methods on the prototype of the native Number object/function. That would allow (5).one()...
In order to avoid having to call toValue at the end of the chain as in #Bergi's solution, you can use a function with attached methods. JS will call toValue automatically when trying to convert to it a primitive type.
function MyNumber(n) {
function x () { }
x.one = function() { n++; return this; };
x.valueOf = function() { return n; };
return x;
}
Then,
MyNumber(5).one().one()
> 7
A nice and general alternative is creating a custom function composition function
var go = function(x, fs){
for(var i=0; i < fs.length; i++){
x = fs[i](x);
}
return x;
}
You can call it like this:
go(5, [one, two, two])
I am personaly not a big fan of method chaining since it restricts you to a predefined set of functions and there is kind of an impedance mismatch between values inside the "chaining object" and free values outside.
Another alternative is to use lodash flow function. For example:
var five = _.flow(one, two, two)
five(5)
I prefer assigning a new chain to a variable. It gives it a clear name and encourages re-use.
Btw, lodash also helps in passing additional arguments to the functions of the chain. For example:
var addFive = _.flow(
_.partialRight(_.add, 1),
_.partialRight(_.add, 2),
_.partialRight(_.add, 2)
)
There are many other useful functions to help in functional chaining, e.g., partial, spread, flip, negate, etc.
Basically there is no function composition in JS. Even if there had been, it would be in the reverse order of what you mention in your question. ie two . two . one because Math declares composition operator like that. The order you want is called piping.
Having said that if you really want composition with dot operator, you may still do it by overloading the . operator via the Proxy object. It's a slightly convoluted topic and you may check this nice blogpost for some ideas.
However the simplest approach for your need would be by reducing an array of functions as;
var pipe = (fs,x,y) => fs.reduce((r,f) => f(r),{x,y}),
fs = [ ({x,y}) => ( x++
, y++
, {x,y}
)
, ({x,y}) => ( x*=3
, y*=3
,{x,y}
)
, ({x,y}) => ( x--
, y--
, {x,y}
)
];
var {x,y} = pipe(fs,1,2);
console.log(x,y);

How to shift "arguments"?

Here's the script:
function runScripts() {
if (arguments.length === 0) return;
chrome.tabs.executeScript(null, {
file: arguments[0]
}, function() {
arguments.shift();
runScripts.apply(null, arguments);
});
}
It doesn't work because arguments is not actually an array, it's just array-like. So how can I "shift" it or hack off the first element so that I can apply this function recursively?
var params = Array.prototype.slice.call(arguments);
params.shift();
You can check out this blog post which explains it in further detail.
I assume you want to reference the original arguments, instead of that from the callback you're passing to chrome.tabs.executeScript.
If so, you'll need to cache it first.
function runScripts() {
if (arguments.length === 0) return;
var args = [];
Array.prototype.push.apply( args, arguments );
chrome.tabs.executeScript(null, {
file: args.shift();
}, function() {
// using the modified Array based on the original arguments object
runScripts.apply(null, args);
});
}
[].shift.call(arguments) is also valid. I'm using this in production code and it works as expected.
With this approach, your function becomes a bit more succinct:
function executeScripts() {
if (arguments.length === 0) return;
chrome.tabs.executeScript(null, {
file: [].shift.call(arguments)
}, function() {
executeScripts.apply(null, arguments);
});
}
If you look on MDN, they state that shift() was implemented with this flexibility in mind.
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/shift
You can transform arguments into a regular array like this:
var args = Array.prototype.slice.call(arguments);
Just wanted to point out a potential problem with [].shift.call(arguments).
This seems to have the perhaps unclear intent of shifting your arguments - even for your function's named parameters - even if used prior to the shift statement.
For example,
function testShift (param1, param2) {
[].shift.call(arguments);
if (param1=="ONE") alert("ONE");
}
If you make the following call, what might you expect to happen?
testShift("ONE", "TWO");
If you expected param1 to stay "ONE", your fix is to set a var to param1 before the shift occurs. It looks like javascript is not binding param1 until the line it is called on - not when the function is called... so modifications to arguments prior to a parameter being used can have unexpected effects.
Hopefully now, you'll be able to expect it.
In ES6 you can now use Array.from() MDN ref
e.g.
const args = Array.from(arguments);
const str = args.shift();
You'll need to convert it to an array and then shift. Or, alternatively, drop the first item when converting to an array. Array.prototype.slice.call(arguments, 1) would work for this.
In newer versions of JS, we can now write:
function f(first, ...rest) { ... }
Or
function f() {
const [first, ...rest] = arguments;
}
Which is a little nicer than "shifting" off the first arg. However, if we did want to, we could first convert arguments into a proper array via Array.from or [...arguments].
Here is an article explains this really well. I copied some key points below.
http://www.javascriptkit.com/javatutors/arrayprototypeslice.shtml
For array, remember you can call the slice function to get a sub array.
var abc = [1,2,3,4,5];
abc.slice(0); //[1,2,3,4,5]
abc.slice(1,3); //[2,3]
Since the argument object is only array like, not really an array. The call() / apply() function basically just "borrow" the slice function from Array and use it on the Argument object, and you can even pass parameters into the slice function just as acting on the array.
var myobject ={ // array-like collection
length: 4,
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three'
}
var myarray = Array.prototype.slice.call(myobject)
// returns myobject as a true array: ["zero", "one", "two", "three"]
var myarray = Array.prototype.slice.call(myobject, 1)
// returns ["one", "two", "three"]
The one remaining question is why we're calling slice() on the prototype object of Array instead of an array instance. The reason is because this is the most direct route to accessing the slice() method of Array when that's all we're interested in; we could have first created an array instance, but that's less efficient and arguably more abstruse:
var myarray = new Array().prototype.slice.call(myobject) // less efficient
You could convert the arguments to an actual array and then use that array in the rest of your logic in the function.
function runScripts()
{
var i=0, l=arguments.length, arr=[];
while(i<l)
{
arr.push(arguments[i++]);
}
...rest of your function code
Edit to add: i've had issues with prototype and call in older versions of IE, so it really depends on what support you'll need.
I went with this:
function executeScripts() {
if (arguments.length === 0) return;
var args = Array.prototype.slice.call(arguments);
chrome.tabs.executeScript(null, {
file: args.shift()
}, function() {
executeScripts.apply(null, args);
});
}
It's useful when writing Google Chrome Extensions. I wanted to use jQuery in my content script, but then you have to load it first. Turns out out by chaining calls to chrome.tabs.executeScript you can do this:
chrome.browserAction.onClicked.addListener(function(tab) {
executeScripts('jquery-1.4.4.min.js', 'content.js');
});

Categories

Resources