currying
This commit is contained in:
parent
799d373a9b
commit
25a77d376a
7 changed files with 308 additions and 307 deletions
|
@ -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.
|
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
|
## Summary
|
||||||
|
|
||||||
Method `func.bind(context, ...args)` returns a "bound variant" of function `func` that fixes the context `this` and first arguments if given.
|
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.
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
libs:
|
|
||||||
- lodash
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Currying and partials
|
|
||||||
|
|
||||||
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 bind `this`?
|
|
||||||
|
|
||||||
The native `bind` does not allow that. We can't just omit the context and jump to arguments.
|
|
||||||
|
|
||||||
Fortunately, a `partial` function 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 that says something now by fixing the first argument
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Currying
|
|
||||||
|
|
||||||
Sometimes people mix up partial function application mentioned above with another thing named "currying". That's another interesting technique of working with functions that we just have to mention here.
|
|
||||||
|
|
||||||
[Currying](https://en.wikipedia.org/wiki/Currying) is a transformation of functions that translates a function from callable as `f(a, b, c)` into callable as `f(a)(b)(c)`. In JavaScript, we usually make a wrapper to keep the original function.
|
|
||||||
|
|
||||||
Currying doesn't call a function. It just transforms it.
|
|
||||||
|
|
||||||
Let's create a helper `curry(f)` function that performs currying for a two-argument `f`. In other words, `curry(f)` for two-argument `f(a, b)` translates it into `f(a)(b)`
|
|
||||||
|
|
||||||
```js run
|
|
||||||
*!*
|
|
||||||
function curry(f) { // curry(f) does the currying transform
|
|
||||||
return function(a) {
|
|
||||||
return function(b) {
|
|
||||||
return f(a, b);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
// usage
|
|
||||||
function sum(a, b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
let carriedSum = curry(sum);
|
|
||||||
|
|
||||||
alert( carriedSum(1)(2) ); // 3
|
|
||||||
```
|
|
||||||
|
|
||||||
As you can see, the implementation is a series of wrappers.
|
|
||||||
|
|
||||||
- The result of `curry(func)` is a wrapper `function(a)`.
|
|
||||||
- When it is called like `sum(1)`, the argument is saved in the Lexical Environment, and a new wrapper is returned `function(b)`.
|
|
||||||
- Then `sum(1)(2)` finally calls `function(b)` providing `2`, and it passes the call to the original multi-argument `sum`.
|
|
||||||
|
|
||||||
More advanced implementations of currying like [_.curry](https://lodash.com/docs#curry) from lodash library do something more sophisticated. They return a wrapper that allows a function to be called normally when all arguments are supplied *or* returns a partial otherwise.
|
|
||||||
|
|
||||||
```js
|
|
||||||
function curry(f) {
|
|
||||||
return function(...args) {
|
|
||||||
// if args.length == f.length (as many arguments as f has),
|
|
||||||
// then pass the call to f
|
|
||||||
// otherwise return a partial function that fixes args as first arguments
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Currying? What for?
|
|
||||||
|
|
||||||
To understand the benefits we definitely need a worthy real-life example.
|
|
||||||
|
|
||||||
Advanced currying allows the function to be both callable normally and partially.
|
|
||||||
|
|
||||||
For instance, we have the logging function `log(date, importance, message)` that formats and outputs the information. In real projects such functions also have many other useful features like sending logs over the network, here we just use `alert`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function log(date, importance, message) {
|
|
||||||
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's curry it!
|
|
||||||
|
|
||||||
```js
|
|
||||||
log = _.curry(log);
|
|
||||||
```
|
|
||||||
|
|
||||||
After that `log` work both the normal way and in the curried form:
|
|
||||||
|
|
||||||
```js
|
|
||||||
log(new Date(), "DEBUG", "some debug"); // log(a,b,c)
|
|
||||||
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can easily make a convenience function for current logs:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// currentLog will be the partial of log with fixed first argument
|
|
||||||
let logNow = log(new Date());
|
|
||||||
|
|
||||||
// use it
|
|
||||||
logNow("INFO", "message"); // [HH:mm] INFO message
|
|
||||||
```
|
|
||||||
|
|
||||||
And here's a convenience function for current debug messages:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let debugNow = logNow("DEBUG");
|
|
||||||
|
|
||||||
debugNow("message"); // [HH:mm] DEBUG message
|
|
||||||
```
|
|
||||||
|
|
||||||
So:
|
|
||||||
1. We didn't lose anything after currying: `log` is still callable normally.
|
|
||||||
2. We were able to generate partial functions such as for today's logs.
|
|
||||||
|
|
||||||
## Advanced curry implementation
|
|
||||||
|
|
||||||
In case you'd like to get in details (not obligatory!), here's the "advanced" curry implementation that we could use above.
|
|
||||||
|
|
||||||
It's pretty short:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function curry(func) {
|
|
||||||
|
|
||||||
return function curried(...args) {
|
|
||||||
if (args.length >= func.length) {
|
|
||||||
return func.apply(this, args);
|
|
||||||
} else {
|
|
||||||
return function(...args2) {
|
|
||||||
return curried.apply(this, args.concat(args2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage examples:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function sum(a, b, c) {
|
|
||||||
return a + b + c;
|
|
||||||
}
|
|
||||||
|
|
||||||
let curriedSum = curry(sum);
|
|
||||||
|
|
||||||
alert( curriedSum(1, 2, 3) ); // 6, still callable normally
|
|
||||||
alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg
|
|
||||||
alert( curriedSum(1)(2)(3) ); // 6, full currying
|
|
||||||
```
|
|
||||||
|
|
||||||
The new `curry` may look complicated, but it's actually easy to understand.
|
|
||||||
|
|
||||||
The result of `curry(func)` is the wrapper `curried` that looks like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// func is the function to transform
|
|
||||||
function curried(...args) {
|
|
||||||
if (args.length >= func.length) { // (1)
|
|
||||||
return func.apply(this, args);
|
|
||||||
} else {
|
|
||||||
return function pass(...args2) { // (2)
|
|
||||||
return curried.apply(this, args.concat(args2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
When we run it, there are two branches:
|
|
||||||
|
|
||||||
1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it.
|
|
||||||
2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result.
|
|
||||||
|
|
||||||
For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`.
|
|
||||||
|
|
||||||
For the call `curried(1)(2)(3)`:
|
|
||||||
|
|
||||||
1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`.
|
|
||||||
2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together.
|
|
||||||
|
|
||||||
As the argument count is still less than 3, `curry` returns `pass`.
|
|
||||||
3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function.
|
|
||||||
|
|
||||||
If that's still not obvious, just trace the calls sequence in your mind or on the paper.
|
|
||||||
|
|
||||||
```smart header="Fixed-length functions only"
|
|
||||||
The currying requires the function to have a known fixed number of arguments.
|
|
||||||
```
|
|
||||||
|
|
||||||
```smart header="A little more than currying"
|
|
||||||
By definition, currying should convert `sum(a, b, c)` into `sum(a)(b)(c)`.
|
|
||||||
|
|
||||||
But most implementations of currying in JavaScript are advanced, as described: they also keep the function callable in the multi-argument variant.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
- When we fix some arguments of an existing function, the resulting (less universal) function is called *a partial*. We can use `bind` to get a partial, but there are other ways also.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
- *Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if arguments count is not enough.
|
|
||||||
|
|
||||||
Currying is great when we want easy partials. As we've seen in the logging example: the universal function `log(date, importance, message)` after currying gives us partials when called with one argument like `log(date)` or two arguments `log(date, importance)`.
|
|
189
1-js/99-js-misc/11-currying-partials/article.md
Normal file
189
1-js/99-js-misc/11-currying-partials/article.md
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
libs:
|
||||||
|
- lodash
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Currying
|
||||||
|
|
||||||
|
[Currying](https://en.wikipedia.org/wiki/Currying) is an advanced technique of working with functions. It's used not only in JavaScript, but in other languages as well.
|
||||||
|
|
||||||
|
Currying is a transformation of functions that translates a function from callable as `f(a, b, c)` into callable as `f(a)(b)(c)`.
|
||||||
|
|
||||||
|
Currying doesn't call a function. It just transforms it.
|
||||||
|
|
||||||
|
Let's see an example first, to better understand what we're talking about, and then practical applications.
|
||||||
|
|
||||||
|
We'll create a helper function `curry(f)` that performs currying for a two-argument `f`. In other words, `curry(f)` for two-argument `f(a, b)` translates it into a function that runs as `f(a)(b)`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
*!*
|
||||||
|
function curry(f) { // curry(f) does the currying transform
|
||||||
|
return function(a) {
|
||||||
|
return function(b) {
|
||||||
|
return f(a, b);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
// usage
|
||||||
|
function sum(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
let carriedSum = curry(sum);
|
||||||
|
|
||||||
|
alert( carriedSum(1)(2) ); // 3
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, the implementation is straightforward: it's just two wrappers.
|
||||||
|
|
||||||
|
- The result of `curry(func)` is a wrapper `function(a)`.
|
||||||
|
- When it is called like `sum(1)`, the argument is saved in the Lexical Environment, and a new wrapper is returned `function(b)`.
|
||||||
|
- Then `sum(1)(2)` finally calls `function(b)` providing `2`, and it passes the call to the original multi-argument `sum`.
|
||||||
|
|
||||||
|
More advanced implementations of currying, such as [_.curry](https://lodash.com/docs#curry) from lodash library, return a wrapper that allows a function to be called both normally and partially:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function sum(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
let carriedSum = _.curry(sum); // using _.carry from lodash library
|
||||||
|
|
||||||
|
alert( carriedSum(1, 2) ); // 3, still callable normally
|
||||||
|
alert( carriedSum(1)(2) ); // 3, called partially
|
||||||
|
```
|
||||||
|
|
||||||
|
## Currying? What for?
|
||||||
|
|
||||||
|
To understand the benefits we need a worthy real-life example.
|
||||||
|
|
||||||
|
For instance, we have the logging function `log(date, importance, message)` that formats and outputs the information. In real projects such functions have many useful features like sending logs over the network, here we'll just use `alert`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function log(date, importance, message) {
|
||||||
|
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's curry it!
|
||||||
|
|
||||||
|
```js
|
||||||
|
log = _.curry(log);
|
||||||
|
```
|
||||||
|
|
||||||
|
After that `log` work both the normal way and in the curried form:
|
||||||
|
|
||||||
|
```js
|
||||||
|
log(new Date(), "DEBUG", "some debug"); // log(a,b,c)
|
||||||
|
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can easily make a convenience function for current logs:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// logNow will be the partial of log with fixed first argument
|
||||||
|
let logNow = log(new Date());
|
||||||
|
|
||||||
|
// use it
|
||||||
|
logNow("INFO", "message"); // [HH:mm] INFO message
|
||||||
|
```
|
||||||
|
|
||||||
|
Now `logNow` is `log` with fixed first argument, in other words "partially applied function" or "partial" for short.
|
||||||
|
|
||||||
|
We can go further and make a convenience function for current debug logs:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let debugNow = logNow("DEBUG");
|
||||||
|
|
||||||
|
debugNow("message"); // [HH:mm] DEBUG message
|
||||||
|
```
|
||||||
|
|
||||||
|
So:
|
||||||
|
1. We didn't lose anything after currying: `log` is still callable normally.
|
||||||
|
2. We were able to generate partial functions such as for today's logs.
|
||||||
|
|
||||||
|
## Advanced curry implementation
|
||||||
|
|
||||||
|
In case you'd like to get in details, here's the "advanced" curry implementation that we could use above.
|
||||||
|
|
||||||
|
It's pretty short:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function curry(func) {
|
||||||
|
|
||||||
|
return function curried(...args) {
|
||||||
|
if (args.length >= func.length) {
|
||||||
|
return func.apply(this, args);
|
||||||
|
} else {
|
||||||
|
return function(...args2) {
|
||||||
|
return curried.apply(this, args.concat(args2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage examples:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function sum(a, b, c) {
|
||||||
|
return a + b + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
let curriedSum = curry(sum);
|
||||||
|
|
||||||
|
alert( curriedSum(1, 2, 3) ); // 6, still callable normally
|
||||||
|
alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg
|
||||||
|
alert( curriedSum(1)(2)(3) ); // 6, full currying
|
||||||
|
```
|
||||||
|
|
||||||
|
The new `curry` may look complicated, but it's actually easy to understand.
|
||||||
|
|
||||||
|
The result of `curry(func)` is the wrapper `curried` that looks like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// func is the function to transform
|
||||||
|
function curried(...args) {
|
||||||
|
if (args.length >= func.length) { // (1)
|
||||||
|
return func.apply(this, args);
|
||||||
|
} else {
|
||||||
|
return function pass(...args2) { // (2)
|
||||||
|
return curried.apply(this, args.concat(args2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
When we run it, there are two execution branches:
|
||||||
|
|
||||||
|
1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it.
|
||||||
|
2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result.
|
||||||
|
|
||||||
|
For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`.
|
||||||
|
|
||||||
|
For the call `curried(1)(2)(3)`:
|
||||||
|
|
||||||
|
1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`.
|
||||||
|
2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together. As the argument count is still less than 3, `curry` returns `pass`.
|
||||||
|
3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function.
|
||||||
|
|
||||||
|
If that's still not obvious, just trace the calls sequence in your mind or on the paper.
|
||||||
|
|
||||||
|
```smart header="Fixed-length functions only"
|
||||||
|
The currying requires the function to have a known fixed number of arguments.
|
||||||
|
```
|
||||||
|
|
||||||
|
```smart header="A little more than currying"
|
||||||
|
By definition, currying should convert `sum(a, b, c)` into `sum(a)(b)(c)`.
|
||||||
|
|
||||||
|
But most implementations of currying in JavaScript are advanced, as described: they also keep the function callable in the multi-argument variant.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
*Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if arguments count is not enough.
|
||||||
|
|
||||||
|
Currying allows to easily get partials. As we've seen in the logging example: the universal function `log(date, importance, message)` after currying gives us partials when called with one argument like `log(date)` or two arguments `log(date, importance)`.
|
|
@ -60,7 +60,7 @@ socket.onerror = function(error) {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
For demo purposes, there's a small server [server.js](demo/server.js) written in Node.js, for the example above, running. It responds with "hello", then waits 5 seconds and closes the connection.
|
For demo purposes, there's a small server [server.js](demo/server.js) written in Node.js, for the example above, running. It responds with "Hello from server, John", then waits 5 seconds and closes the connection.
|
||||||
|
|
||||||
So you'll see events `open` -> `message` -> `close`.
|
So you'll see events `open` -> `message` -> `close`.
|
||||||
|
|
||||||
|
|
BIN
figures.sketch
BIN
figures.sketch
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue