currying
This commit is contained in:
parent
799d373a9b
commit
25a77d376a
7 changed files with 308 additions and 307 deletions
16
1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md
Normal file
16
1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
|
||||
1. Either use a wrapper function, an arrow to be concise:
|
||||
|
||||
```js
|
||||
askPassword(() => user.login(true), () => user.login(false));
|
||||
```
|
||||
|
||||
Now it gets `user` from outer variables and runs it the normal way.
|
||||
|
||||
2. Or create a partial function from `user.login` that uses `user` as the context and has the correct first argument:
|
||||
|
||||
|
||||
```js
|
||||
askPassword(user.login.bind(user, true), user.login.bind(user, false));
|
||||
```
|
34
1-js/06-advanced-functions/10-bind/6-ask-partial/task.md
Normal file
34
1-js/06-advanced-functions/10-bind/6-ask-partial/task.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Partial application for login
|
||||
|
||||
The task is a little more complex variant of <info:task/question-use-bind>.
|
||||
|
||||
The `user` object was modified. Now instead of two functions `loginOk/loginFail`, it has a single function `user.login(true/false)`.
|
||||
|
||||
What to pass `askPassword` in the code below, so that it calls `user.login(true)` as `ok` and `user.login(false)` as `fail`?
|
||||
|
||||
```js
|
||||
function askPassword(ok, fail) {
|
||||
let password = prompt("Password?", '');
|
||||
if (password == "rockstar") ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
let user = {
|
||||
name: 'John',
|
||||
|
||||
login(result) {
|
||||
alert( this.name + (result ? ' logged in' : ' failed to log in') );
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
askPassword(?, ?); // ?
|
||||
*/!*
|
||||
```
|
||||
|
||||
Your changes should only modify the highlighted fragment.
|
||||
|
|
@ -196,8 +196,124 @@ for (let key in user) {
|
|||
JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash.
|
||||
````
|
||||
|
||||
## Partial functions
|
||||
|
||||
Until now we have only been talking about binding `this`. Let's take it a step further.
|
||||
|
||||
We can bind not only `this`, but also arguments. That's rarely done, but sometimes can be handy.
|
||||
|
||||
The full syntax of `bind`:
|
||||
|
||||
```js
|
||||
let bound = func.bind(context, [arg1], [arg2], ...);
|
||||
```
|
||||
|
||||
It allows to bind context as `this` and starting arguments of the function.
|
||||
|
||||
For instance, we have a multiplication function `mul(a, b)`:
|
||||
|
||||
```js
|
||||
function mul(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
```
|
||||
|
||||
Let's use `bind` to create a function `double` on its base:
|
||||
|
||||
```js run
|
||||
function mul(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
*!*
|
||||
let double = mul.bind(null, 2);
|
||||
*/!*
|
||||
|
||||
alert( double(3) ); // = mul(2, 3) = 6
|
||||
alert( double(4) ); // = mul(2, 4) = 8
|
||||
alert( double(5) ); // = mul(2, 5) = 10
|
||||
```
|
||||
|
||||
The call to `mul.bind(null, 2)` creates a new function `double` that passes calls to `mul`, fixing `null` as the context and `2` as the first argument. Further arguments are passed "as is".
|
||||
|
||||
That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one.
|
||||
|
||||
Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`.
|
||||
|
||||
The function `triple` in the code below triples the value:
|
||||
|
||||
```js run
|
||||
function mul(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
*!*
|
||||
let triple = mul.bind(null, 3);
|
||||
*/!*
|
||||
|
||||
alert( triple(3) ); // = mul(3, 3) = 9
|
||||
alert( triple(4) ); // = mul(3, 4) = 12
|
||||
alert( triple(5) ); // = mul(3, 5) = 15
|
||||
```
|
||||
|
||||
Why do we usually make a partial function?
|
||||
|
||||
The benefit is that we can create an independent function with a readable name (`double`, `triple`). We can use it and not provide first argument of every time as it's fixed with `bind`.
|
||||
|
||||
In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience.
|
||||
|
||||
For instance, we have a function `send(from, to, text)`. Then, inside a `user` object we may want to use a partial variant of it: `sendTo(to, text)` that sends from the current user.
|
||||
|
||||
## Going partial without context
|
||||
|
||||
What if we'd like to fix some arguments, but not the context `this`? For example, for an object method.
|
||||
|
||||
The native `bind` does not allow that. We can't just omit the context and jump to arguments.
|
||||
|
||||
Fortunately, a helper function `partial` for binding only arguments can be easily implemented.
|
||||
|
||||
Like this:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
function partial(func, ...argsBound) {
|
||||
return function(...args) { // (*)
|
||||
return func.call(this, ...argsBound, ...args);
|
||||
}
|
||||
}
|
||||
*/!*
|
||||
|
||||
// Usage:
|
||||
let user = {
|
||||
firstName: "John",
|
||||
say(time, phrase) {
|
||||
alert(`[${time}] ${this.firstName}: ${phrase}!`);
|
||||
}
|
||||
};
|
||||
|
||||
// add a partial method with fixed time
|
||||
user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());
|
||||
|
||||
user.sayNow("Hello");
|
||||
// Something like:
|
||||
// [10:00] John: Hello!
|
||||
```
|
||||
|
||||
The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that calls `func` with:
|
||||
- Same `this` as it gets (for `user.sayNow` call it's `user`)
|
||||
- Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`)
|
||||
- Then gives it `...args` -- arguments given to the wrapper (`"Hello"`)
|
||||
|
||||
So easy to do it with the spread operator, right?
|
||||
|
||||
Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library.
|
||||
|
||||
## Summary
|
||||
|
||||
Method `func.bind(context, ...args)` returns a "bound variant" of function `func` that fixes the context `this` and first arguments if given.
|
||||
|
||||
Usually we apply `bind` to fix `this` in an object method, so that we can pass it somewhere. For example, to `setTimeout`. There are more reasons to `bind` in the modern development, we'll meet them later.
|
||||
Usually we apply `bind` to fix `this` for an object method, so that we can pass it somewhere. For example, to `setTimeout`.
|
||||
|
||||
When we fix some arguments of an existing function, the resulting (less universal) function is called *partially applied* or *partial*.
|
||||
|
||||
Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a `send(from, to)` function, and `from` should always be the same for our task, we can get a partial and go on with it.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue