简体   繁体   中英

How to get parameter of an arrow function inside another function?

I want to create a functions that returns the parameter of another function. I know I can use argument or rest operator to access parameter inside a function itself, but how can I get them outside that function?

const returnValue = (fn) => {
 //How can I get the parameter of fn? Assume I know its arity.
}

For example:

const foo = 'bar'
const hello = 'world'

const returnFirstArgument = (val1, val2) => val1

const returnArgumentByPosition = (fn, num) => {
//Take fn as input, return one of its parameter by number
}

const returnSecondArgument = returnArgumentByPosition(returnFirstArgument(foo, hello), 1) //Expect it returns 'world'

What you want isn't possible to do without modifying how returnFirstArgument behaves. Take for example the below piece of code:

const x = 1 + 2;
console.log(x); // 3

Before a value is assigned to x , the expression 1 + 2 needs to be evaluated to a value. In this case 1 + 2 gets evaluated to 3 , so x gets assigned to 3 , that way when we print it, it prints the literal number 3 out in the console. Since it is now just a number, we can't tell how 3 was derived (it could have come from 0 + 3 , 1 * 3 , etc...).

Now take a similar example below:

const max = Math.max(1, 2);
console.log(max); // 2

The same idea here applies from above. First Math.max(1, 2) is evaluated to the value of 2 , which is then assigned to max . Again, we have no way of telling how 2 was derived.

Now consider a function:

const add = (x, y) => x + y;
const ans = add(1 + 2, Math.max(1, 2));
console.log(ans); // 5

When we call the function, the function's arguments are first evaluated to values. The parameters within the function are then assigned to copies of these values:

const ans = add(1 + 2, Math.max(1, 2));
//               ^--------^------------- both evaluate first before function is invoked

so the above function call becomes:

const ans = add(3, 2);

As a result, inside the add function, x becomes 3 and y becomes 2 . Just like with the above first two examples with variables, we have no way of knowing the 3 came from the expression 1+2 and that 2 came from the function call of Math.max(1, 2) .

So, relating this back to your original question. Your function call is analogous to the add function call shown above:

const returnSecondArgument = returnArgumentByPosition(returnFirstArgument(foo, hello), 1)

just like in the other examples, the arguments passed to the function can't be expressions, so they need to be evaluated first to values. returnFirstArgument(foo, hello) is evaluated to a value before the returnArgumentByPosition function is invoked. It will evaluate to the string "bar" . This results in fn becoming "bar" inside of your returnArgumentByPosition . As "bar" is just a string, we again have to way of telling where it came from, and so, won't have access to the function which created it. As a result, we can't access the second argument of the function, since this information is not retained anywhere.


One approach to do what you're after is to create a recall function. The recall function is able to "save" the arguments you passed into it, and then expose them later. Put simply, it wraps your original function but is able to save the arguments and the result of calling your original function:

 const recall = fn => (...args) => { return { args, result: fn(...args), } }; const add = recall((x, y) => x + y); const getN = ({args}, n) => { return args[n]; } const res = getN(add(1, 2), 1); console.log(res);

The above approach means that add() will return an object. To get the result of calling add, you can use .result . The same idea applies to get the arguments of add() . You can use .args on the returned object. This way of saving data is fine, however, if you want a more functional approach, you can save the data as arguments to a function:

 const recall = fn => (...args) => { return selector => selector( args, // arguments fn(...args) // result ); }; // Selectors const args = args => args; const result = (_, result) => result; const getN = (wrapped, n) => { return wrapped(args)[n]; } const add = recall((x, y) => x + y); const wrappedAns = add(1, 2); const nth = getN(wrappedAns, 1); console.log(nth); // the second argument console.log(wrappedAns(result)); // result of 1 + 2

above, rather than returning an object like we were before, we're instead returning a function of the form:

return selector => selector(args, fn(...args));

here you can see that selector is a function itself which gets passed the arguments as well as the result of calling fn() (ie: your addition function). Above, I have defined two selector functions, one called args and another called result . If the selector above is the args function then it will be passed args as the first argument, which it then returns. Similarly, if the selector function above is the result function, it will get passed both the args and the result of calling fn , and will return the result the return value of fn(...args) .

Tidying up the above (removing explicit returns etc) and applying it to your example we get the following:

 const foo = 'bar'; const hello = 'world'; const recall = fn => (...args) => sel => sel(args, fn(...args)); const returnFirstArgument = recall((val1, val2) => val1); const returnArgumentByPosition = (fn, num) => fn(x => x)[num]; const returnSecondArgument = returnArgumentByPosition(returnFirstArgument(foo, hello), 1); console.log(returnSecondArgument); // world


Side note (for an approach using combinators):

In functional programming, there is a concept of combinators. Combinators are functions which can be used as a basis to form other (more useful) functions.

One combinator is the identity-function, which simply takes its first argument and returns it:

const I = x => x;

Another combinator is the K-combinator, which has the following structure:

const K = x => y => x;

You may have noticed that the first selector function args is missing an argument. This is because JavaScript doesn't require you to enter all the parameters that are passed as arguments into the function definition, instead, you can list only the ones you need. If we were to rewrite the args function so that it showed all the arguments that it takes, then it would have the following structure:

const args = (args, result) => args;

If we curry the arguments of this function, we get:

const args = args => result => args;

If you compare this function to the K-combinator above, it has the exact same shape. The K-combinator returns the first curried argument, and ignores the rest, the same applies with our args function. So, we can say that args = K .

Similarly, we can do a similar thing for the result selector shown above. First, we can curry the arguments of the results selector:

const result = _ => result => result;

Notice that this almost has the same shape as the K combinator, except that we're returning the second argument rather than the first. If we pass the identify function into the K-combinator like so K(I) , we get the following:

const K = x => y => x;
K(I) returns y => I

As we know that I is x => x , we can rewrite the returned value of y => I in terms of x:

y => I
can be written as...
y => x => x;

We can thenalpha-reduce (change the name of y to _ and x to result ) to get _ => result => result . This now is the exact same result as the curried result function. Changing variable names like this is perfectly fine, as they still refer to the same thing once changed.

So, if we modify how selector is called in the recall function so that it is now curried, we can make use of the I and K combinators:

 const I = x => x; const K = x => y => x; const recall = fn => (...args) => sel => sel(args)(fn(...args)); const args = K; const result = K(I); const getN = (fn, n) => fn(args)[n]; const add = recall((x, y) => x + y); const addFn = add(1, 2); const nth = getN(addFn, 1); console.log(nth); // the second argument console.log(addFn(result)); // result of 1 + 2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM