Relating to the following mock code (_ relates to the lodash library):
var containerFunction = function () {
var opts = {data: value};
_.map(lines, functionForEach);
}
var functionForEach = function (line) {
Do something for each line passed in from the maps function.
Need to access opts in this scope.
return value;
}
The argument line is received from the map function, but what would be the best way of passing the opts argument to the functionForEach function while keeping the rough pattern above (if possible).
I thought something like:
_.map(lines, functionForEach(opts))
or something similar might work but obviously doesn't.
Any suggestions?
You have three alternatives:
Put functionForEach inside containerFunction. If you are not going to use functionForEach elsewhere, this makes the most sense.
Write it as:
var containerFunction = function () {
var opts = {data: value};
_.map(lines, function(elt) { return functionForEach(elt, opts); });
}
var functionForEach = function (line, opts) {
Do something for each line passed in from the maps function.
Need to access opts in this scope.
return value;
}
If you must, pass opts as the third (thisArg) argument to _.map and access it using this inside functionForEachvar.
Lodash has good utilities for function composition, including partially-applying arguments. However, this isn't actually so straightforward. Let's say we want to use partialRight():
function functionForEach(item, i) { return item * i; }
_.map([1, 2, 3], _.partialRight(functionForEach, 2));
// → [0, 2, 6]
Oops. That's not correct. The reason is that we're not getting the arguments passed to functionForEach() as we expect them. We want the i argument to always have a value of 2 and the item is the mapped collection value.
Let's try something else:
_.map([1, 2, 3], _.ary(_.partialRight(functionForEach, 2), 1));
// → [2, 4, 6]
That's better. The problem is that map() passes the item as the first argument, along with 2 other arguments (the index and the collection itself). Since we don't care about these latter two arguments, we can use ary() to compose a new function that ignores them.
Here's another approach:
_.map([1, 2, 3], _.partial(_.rearg(functionForEach, 1, 0), 2));
// → [2, 4, 6]
This time, we're using rearg() to change the order of our partial() function arguments. This approach I find to be less intuitive than simple going right for partialRight().
One final example:
_.map([1, 2, 3], _.flow(_.identity, _.partialRight(functionForEach, 2)));
// → [2, 4, 6]
Here, we're using the flow() higher-order function to compose our callback. This function takes the arguments supplied to it, and chains together the functions we pass to it - the output of the last function is the input to the next. We're using the identity() function here because it simply returns the first argument passed to it, which is exactly what we want.
This last approach is similar to the ary() approach - it's doing the same thing. The flow() approach is superior if we ever wanted to build on this behavior by passing in more functions, before or after functionForEach().
Take a look at _.partial. Docs:
partial
var containerFunction = function() {
var opts = { data: value };
_.map(lines, _.partial(functionForEach, opts));
};
var functionForEach = function (opts, line) { ... };
_.partial returns a new function with the arguments prepended to the functionForEach. In the example above, _.partial returns the following function signature:
function (line) { ... }
Calling the return function with a line calls functionForEach with line and opts defaulted to whatever you passed in as the argument in _.partial.
Related
I'm working through Eloquent Javascript and I'm having trouble understanding something. Perhaps I've missed something along the way. This is the solution given for chapter 5 (higher-order functions), exercise 1, which takes the elements in the different arrays and puts them all in a single array:
var arrays = [[1, 2, 3], [4, 5], [6]];
console.log(arrays.reduce(function(flat, current) {
return flat.concat(current);
}, []));
My problem is: I have absolutely no clue why the arguments "flat" and "current" work in this situation. The entire chapter reads through assuming the reader understands what's going on here but I have absolutely no idea why this works. It doesn't appear that "flat" and "current" are defined anywhere. Another short example is this one where the author explains how the reduce method works (problem area in bold):
function reduce(array, combine, start) {
var current = start;
for (var i = 0; i < array.length; i++)
current = combine(current, array[i]);
return current;
}
**console.log(reduce([1, 2, 3, 4], function(a, b) {
return a + b;
}, 0));**
Where in the world did "a" and "b" come from and why does this piece of code work? Any help would be much appreciated, thank you.
flat and current don't need to be declared anywhere, they are parameters to the anonymous function that is passed to Array.reduce.
One way to illustrate this is to modify your second example to use Array.reduce directly using an anonymous function with parameters a and b. Look at this:
[1, 2, 3, 4].reduce(function(a, b) {
console.log("a: " + a + " b: " + b);
return a + b;
});
The console will now show:
a: 1 b: 2
a: 3 b: 3
a: 6 b: 4
10
What's happening is that the anonymous function(a, b) {...} is called with (1, 2), which returns 3, which is passed in again (3, 3) which returns 6, which is passed in as the first argument (6, 4), which returns the final answer 10.
Another illustration is to use a second argument to Array.reduce, say 10, to see what's going on. That 10 is used as the initialValue. So:
[1, 2, 3, 4].reduce(function(a, b) {
console.log("a: " + a + " b: " + b);
return a + b;
}, 10);
The trace is:
a: 10 b: 1
11 b: 2
13 b: 3
16 b: 4
20
You can work out how that happened.
Yes, reduce can be a little confusing in the beginning. It is a native function that takes two parameters. One is a callback function and the other one is any value.
The idea is that in the callback function you can use the values of the array one at a time to process a result. To do that it iterates over the array values and passes them to the callback function you defined one at a time and for every loop it takes the value of the last loop and passes it as well.
Let's say you want to sum all numbers in an array:
//your array
var numbers = [4,7,3];
//your initial value
var initialValue = 0;
//your function
function sum(iteratingValue, arrayValue) {
return iteratingValue + arrayValue;
}
var result = numbers.reduce(sum, initialValue);
Now, you can name your callback function parameters whatever you like, a and b, start and finish, fish and duck. It won't matter, reduce will pass the values in the same order.
Here is the definition by MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
reduce executes the callback function once for each element present in the array, excluding holes in the array, receiving four arguments: the initial value (or value from the previous callback call), the value of the current element, the current index, and the array over which iteration is occurring.
First, let us touch the core of the question. Let's suppose you have a function, like this:
function func1(myfunc, arr) {
//Do something, which will result in having variables called param1 and param2
myfunc(param1, param2);
}
Let's see what happens. func1 takes myfunc and arr as parameters. myfunc will be passed when func1 is called. arr is an array.
Now, let's suppose you call it this way:
func1(function(a, b) {
//do something with a and b
}, [1, 2, 3, 4]);
You are calling func1, by passing a function and an array. The array is obvious, so let's see the function. The function expects two parameters and will do something with them. You do not have to define a or b when you call func1, since it is func1's internal job to create and initialize them. So, func1 will do its internal things and call your function passed as a parameter. Now, let's see your example:
var arrays = [[1, 2, 3], [4, 5], [6]];
console.log(arrays.reduce(function(flat, current) {
return flat.concat(current);
}, []));
Here, you call arrays.reduce (which is very similar to func1 in the general description). You pass a function and an array. Again, the array is obvious, but the question is, how flat and current are defined. The answer is that it is arrays.reduce's internal job to create and initialize them. As about the reduce prototype function, you can read more about it here.
I found an implementation using using prototypes. This is a simplification to show the structure:
function Thingie(){
this.content = [];
}
Thingie.prototype = {
push: function(arg) {
this.content.push(arg);
},
pop: function() {
return this.content.pop();
}
};
var t = new Thingie();
forEach([10, 3, 4, 8, 2, 9, 7, 1, 2, 6, 5],
method(t, "push"));
What is "method" in the example on the last line? I've never seen this construct. I use t.push like everyone else.
I tried find how "method()" is defined online, but it is impossible to search for a function called "method" using any possible set of search terms. All you get are how functions and methods are defined and used. There also seems to be no information when I look at forEach documentation.
Does this make sense to anybody?
method(t, "push")
would be defined as:
function method(obj, name) {
return obj[name].bind(obj);
}
That forEach looks like UnderscoreJS's function _.each
_.each(list, iteratee, [context]) Alias: forEach
example:
_.each([1,2,3], function(item) { console.log(item); });
// console output:
// 1
// 2
// 3
That method probably looks like this (Beware: wild guess!) , giving you the function to use as iteratee paramater
function method(obj, name){
if(typeof(obj[name]) != "function")
throw new Error("Not a function");
return obj[name];
}
A function like that is lodash.bindKey, which does exactly what you want. Also, the forEach method could be lodash.forEach, or simply Array.prototype.forEach, which is built-in.
[1,2,3].forEach(_.bindKey(t, 'push'));
However, this works because Thinghie#push expects only one argument. If the same call would be made on an array, the result would not be as expected, since forEach methods take 3 arguments: value, index, array, and [].push can handle multiple arguments. So, the code
var array = [];
[1,2].forEach(_.bindKey(array, 'push'));
console.log(array); // outputs [1, 0, [1, 2], 2, 1, [1, 2]]
In this case (and in any case when we would like the function returned by method applied on only one argument), I guess the solution is to write
function method(obj, name) {
return function(arg) { return obj[name](arg); }
}
This might be a duplicate but I had little success in finding the right answer. I'm trying to achieve something like this :
var joinArrays = function(myCollectionOfArguments) {
return array.concat(array1, array2, ..... , arrayN);
};
In which case myCollectionOfArguments will be array1, array2, ..... , arrayN. How could I achieve something like that ? I do know that if I had a callback function I cold pass as many arguments as I would like using .apply(), but in this certain case I'm a bit confused on the approach.
EDIT : So, to be more descriptive : Instead of passing just one argument, I would to be able to pass as many as I want without having to specify it when I define the function's arguments, in my case myCollectionOfArguments, would be just one argument when defining the function, but when I want to use the function I want to be able to pass more than one argument ;
You can use the arguments object for an arbitrary amount of arguments, and you can even pass that directly into .apply:
var joinArrays = function() {
return [].concat.apply([], arguments);
};
Btw, using bind would be more elegant here:
var joinArrays = Array.prototype.concat.bind([]);
If I get your intention right, try:
var joinArrays = function(a) {
return a.concat.apply(a, Array.prototype.slice.call(arguments, 1));
};
var foo = joinArrays([1, 2], [3, 4], [5, 6]);
console.log(foo); // => [1, 2, 3, 4, 5, 6]
function testFunction()
{
var arg1 = arguments[0];
var arg2 = arguments[1];
var arg3 = arguments[2];
}
Not sure I understand, but you can pass arbitrary number of arrays:
joinArrays([1,2,3], [4,5,6], [7,8], [9,10,11,12])
and later read all of them using special arguments variable inside joinArrays().
While going through Eloquent Javascript (Chapter 6) there is a reference to higher-order functions in Javascript. While there is an example provided in Chapter 3, I believe it could be a bit simpler since I still don't fully understand the concept. After searching the web I can't seem to find any succinct examples of a higher-order function.
I'd like to see a basic/simple higher-order function in Javascript that will explain the concept.
Higher functions are concepts from functional programming. In briefly, a higher function is a function which takes another function as parameter. In javascript, some higher functions are added recently.
Array.prototype.reduce
//With this function, we can do some funny things.
function sum(array){
return array.reduce(function(a, b){ return a + b; }, 0);
}
So, in the above sample, reduce is a higher order function, it takes another function, the anonymous function in the sample, as a parameter. The signature of reduce looks like this
reduce(func, init);
//func is a function takes two parameter and returns some value.
// init is the initial value which would be passed to func
//when calling reduce, some thing happen
//step 1.
[1, 2, 3, 4, 5].reduce(function(a, b){ return a + b }, 0);
//step 2.
[2, 3, 4, 5].reduce(function(a, b){ return a + b}, 0 + 1);
//step 3.
[3, 4, 5].reduce(function(a, b){ return a + b}, 0 + 1 + 2);
//...
As you can see, reduce iterate an array, and apply the func with init and first element of that array, then bind the result to init.
Another higher order funciton is filter.
Array.prototype.filter
//As the name indicates, it filter out some unwanted values from an Aarry. It also takes a function, which returns a boolean value, true for keeping this element.
[1, 2, 3, 4, 5].filter(function(ele){ return ele % 2 == 0; });
With the above two examples, I have to say higher order function is not that much easy to understand, especially reduce. But that's not complex, with higher order function, actually your code would be more clean and readable. Take the filter as example, it tells people that it throws all odd numbers away.
Here I'd like to implement a simple filter function to show you how.
function filter(array, func){
var output = [];
for(var i = 0; i < array.length; i++){
if(func(array[i])) output.push(array[i]);
}
return output;
}
All you know that arguments is a special object that holds all the arguments passed to the function.
And as long as it is not an array - you cannot use something like arguments.slice(1).
So the question - how to slice everything but first element from arguments?
UPD:
seems like there is no way without converting it to an array with
var args = Array.prototype.slice.call(arguments);
If someone posts another solution it would be great, if not - I'll check the first one with the line above as an answer.
Q. How to slice everything but first element from arguments?
The following will return an array containing all arguments except the first:
var slicedArgs = Array.prototype.slice.call(arguments, 1);
You don't have to convert arguments to an array first, do it all in one step.
Meddling with array functions is not actually necessary.
Using rest parameter syntax ...rest is cleaner and more convenient.
Example
function argumentTest(first, ...rest) {
console.log("First arg:" + first);
// loop through the rest of the parameters
for (let arg of rest) {
console.log("- " + arg);
}
}
// call your function with any number of arguments
argumentTest("first arg", "#2", "more arguments", "this is not an argument but a contradiction");
...Rest
See the example Fiddle
See MDN Documentation page
From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments:
You should not slice on arguments because it prevents optimizations in
JavaScript engines (V8 for example). Instead, try constructing a new
array by iterating through the arguments object.
So Paul Rosiana's answer above is correct
This can be a way:
var args = Array.from(arguments).slice(1);
You can "slice without slicing" by procedurally walking the arguments object:
function fun() {
var args = [];
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
return args;
}
fun(1, 2, 3, 4, 5); //=> [2, 3, 4, 5]
You can use the method [].slice.call(arguments, 1)
[].slice will return you the slice function object and you can call it as the arguments and 1 are the parameters
You can use ...rest within the function to separate the first and the rest of the arguments:
function foo(arr) {
const [first, ...rest] = arguments;
console.log(`first = ${first}`);
console.log(`rest = ${rest}`);
}
//Then calling the function with 3 arguments:
foo(1,2,3)
you can use this too
function arg(myArr) {
let arg = Object.values(arguments).slice(2, 4);
console.log(arg);
return arg;
};
arg([1, 2, 3], 4, [5,6], 7)
see here for reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
...
Arguments type is iterable, so using the ES6 (ES2015) spread ... operator, then use Array.slice([start], [end]) method, such as
function omitFirstAndLastArgument(value) {
const args = arguments.length > 2 ? [...arguments].slice(1, -1) : [];
return args;
}
omitFirstAndLastArgument(1, 2, 3, 4, 5, 6); // [2, 3, 4, 5]