1

Functional Programming: The Power of Currying | Bits and Pieces

 2 years ago
source link: https://blog.bitsrc.io/functional-programming-part-3-the-powers-of-currying-213eb69b234b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

What is Currying

A curried function is a function that keeps returning functions until all its params are fulfilled

How Currying Works

Let’s say we have add function

const add = (a, b) => a + b

The simplest implementation of currying is to make a function return a function and so on, like:

const add = (a) => (b) => a + b

Where that can be used like this:

const addOne = add(1) // addOne = (b) => 1 + b

But let’s imagine that we have a curry function that takes the function and curry it. For example:

const add = curry((a, b) => a + b)

As we see, curry is a function that takes another function to lazi-fy the params. So now we can invoke it like this:

const addOne = add(1) // addOne = (b) => 1 + b

So, first we created addOne by passing 1 as a first param (a) to the curried add function. Which yielded another function that waits for the rest of the params, where the logic of add will not be executed until all params are provided.

addOne(2) // 3

Now, passing 2 (as b) to addOne; executes the logic 1 + 2

Quick conclusion:

curry function takes a function and makes its params lazy, in other words you provide these params as you need/go. Just like addOne

Quick note:

You still can call the curried version of add function like this:

const three = add(1, 2)

So it either takes the arguments piece by piece or all the arguments at once.

Why Currying

Currying will make our code:

  1. Cleaner
  2. Less repetitive params passing and less verbose code
  3. More composable
  4. More reusable

Why Currying Makes our Code Better

Mainly, some functions take “config” data as input

If we have functions that take “config” params, we better curry them because these “configs” will probably be repeated over and over again.

For example, let’s suppose we have a translator function, that takes a locale and a text to be translated:

const translator = (locale, text) => {/*translation*/}

The usage would look like this:

translator('fr', 'Hello')
translator('fr', 'Goodbye')
translator('fr', 'How are you?')

Every time we call translator we should provide locale and text. Which is redundant and dirty to provide the locale on every call.

But instead, let’s curry translator like this:

const translator = curry((locale, text) => {/*translation*/})const inFrench = translator('fr') 

Now inFrench has fr as locale provided to the curried translator function and waits for text to be provided. We can use it like this:

inFrench('Hello')
inFrench('Goodbye')
inFrench('How are you?')

Currying did us a great favour indeed, we don’t need to specify the locale each time, instead the curried inFrench has locale due to currying.

After currying -in this specific example. Code is:

  1. Cleaner
  2. Less verbose and less redundant

Because we separated “config” from actual “data”. Which is quite handy in many areas and use cases.

In real life

In practice we have dynamic locale (each user has a different language) might be fr, en, de or anything. So instead we better rename inFrench to translate, where translate can be loaded with any locale.

Now we have translator that takes a locale as “config” and text as data. Due to the fact that translator is curried, we were able to separate “config” from “data” params.

Why separate “config” from “data” params?

Many components and functions need the use of some functionalities (translate in our case) but shouldn’t or can’t know about the “config” part (locale). Where these components or functions have the “data” only part (text). So these functions will be able to use that function without the need of knowing about the “config” part.

Thus, that component or function will be less coupled with the system, which will make the components more composable and more maintainable.

When do we apply this idea

When we know that there is “config” and there is “data” in a function, we better curry it.

Currying will give us the ability to separate them. And that is a sign of a mature system design. Because one of the large pillars of code qualities is separation of concerns.

Even if a function needs all the params to operate well, we still know better when to pass the params and on which layer of that app.

Closure and Currying Relationship

A Closure: is a function returned by a “parent” function and has access to the parent function’s internal state. (described earlier here)

Currying: will always result a closure. Because each function returned by a curried function will be provided with parents’ internal state.

More Examples

Before we dive deeper

Let’s introduce some utilities, so we can have a deeper look.

Array prototype has utilities like filter, map and others. But they are not curry-able, because they use dot (.) notation.

So let’s convert them to curry-able format:

const filter     = (fn, list)   => list.filter(fn)
const map = (fn, list) => list.map(fn)
const startsWith = (starter, s) => s.startsWith(starter)

Now we can use them like this:

const lessThan18 = user => user.age < 18// Converting this format
const filteredUsers = users.filter(lessThan18)
// To this format instead
const filteredUsers = filter(lessThan18, users)

(We eliminated the dot notation, and passed processed data as a last param)

Then we curry them. Where this curry function will take a function and return a curried function (you can find the implementation here):

const filter     = curry((fn, list)   => list.filter(fn))
const map = curry((fn, list) => list.map(fn))
const startsWith = curry((starter, s) => s.startsWith(starter))

Examples

Now we can do some meaningful examples…

Example 1️:

Given a list of numbers, increment all numbers by 1

Input: [1, 2, 3, 4, 5]

Output: [2, 3, 4, 5, 6]

Solution:

// the curried `add` function that we defined earlier
const
addOne = add(1)const incrementNumbers = map(addOne)const incrementedNumbers = incrementNumbers(numbers)

Example 2️:

Given a string, keep all words that start with ‘C’ letter

Input: "currying is awesome”

Output: “currying”

Solution:

const startsWithC = startsWith('c')const filterStartsWithC = filter(startsWithC)const filteredWords = filterStartsWithC(words)

Example 3️:

Given a list of ranges and a list of numbers; Create an array of functions that can filter numbers based on provided ranges.

Input:

const ranges = [
{min: 10, max: 100},
{min: 100, max: 500},
{min: 500, max: 999}
]const numbers = [30, 50, 110, 200, 650, 700, 1000]// 30 & 50 within 1st range
// 110 & 200 within 2nd range
// 650 & 700 within 3rd range
// 1000 isn't in any range

Output: an array of functions; Each function can take numbers and return filtered numbers that are within the given range.

Solution:

const isInRange = curry(
(range, val) => val > range.min && val < range.max
)const filters = ranges.map((range) => filter(isInRange(range)))

This example has double curry if you can spot it, the filter and the isInRange

filters now is a list of functions, each is waiting for numbers to process, loaded (“config”-ed) with min and max

Explanation:

The best explanation would be to unfold the currying, and use regular functions instead…

const isInRange = (range, val) => val > range.min && val < range.maxconst filters = ranges.map(
(range) => (numbers) => numbers.filter(
number => isInRange(range, number)
)
)

Remember, () => () => ... is still another implementation of currying. The simple version of currying.

And all thanks to currying! ❤️

Conclusion

Currying is just about making the params lazy. Where the function keeps returning function until all of its arguments are fulfilled then it computes and returns the result.

We also saw how it makes our code cleaner, less verbose, more composable and even more reusable through practical examples. And that leveraged separation of concerns principle.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK