Functional Programming (Part 1)

This series of articles is not intended for beginners to the front end, but requires some programming experience and an understanding of JavaScript concepts such as scopes and closures

Combination function

Composition is a simple, elegant, and expressive way of clearly modeling the behavior of software. The process of creating larger software components and functions by combining small, deterministic functions results in software that is easier to organize, understand, debug, extend, test, and maintain.

For combination, I think it is functional programming inside one of the most essence places, so I can’t wait to take out this concept is introduced first, because in the whole study, functional programming, mostly encountered in combination way to write the code, it’s change you from an object oriented, or a key point of structured programming ideas.

I’m not going to prove that composition is better than inheritance, or that composition is a good way to write code, but I hope you’ve read this article and learned that abstracting code in composition will broaden your horizons and give you ideas when you want to refactor your code or write more maintainable code.

The concept of composition is very intuitive, not unique to functional programming, but ubiquitous in our lives and in front-end development.

Applications such as we are now popular SPA (single page), will have the concept of component, why should have the concept of component, because its purpose is to want you to have some common features or elements combination abstract into reusable components, if not universal, when building a complex page you can also split into each component has a simple function, Then assemble your pages to meet your various needs.

In fact, our functional programming in the composition is similar, function composition is a simple task has been broken down into a complex whole process.

Now we have a requirement: give you a string, convert the string to uppercase, and reverse the order.

You might write it like this.

/ / 1.1

var str = 'function program'

// One line of code
function oneLine(str) {
    var res = str.toUpperCase().split(' ').reverse().join(' ')
    return res;
}

// Or follow the instructions step by step, first uppercase and then reverse order
function multiLine(str) {
    var upperStr = str.toUpperCase()
    var res = upperStr.split(' ').reverse().join(' ')
    return res;
}

console.log(oneLine(str)) // MARGORP NOITCNUF
console.log(multiLine(str)) // MARGORP NOITCNUF
Copy the code

You probably didn’t see anything wrong here, but now the product has taken A fancy and changed the requirement to capitalize the string and assemble each character into an array. For example, ‘aaa’ will eventually become [A, A, A].

At this point we need to change the function we encapsulated earlier. This modifies previously encapsulated code and breaks the open close principle in design mode.

So if we write our initial requirements code like this, we write it in a functional way.

/ / 1.2

var str = 'function program'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split(' ').reverse().join(' ')}varToUpperAndReverse = combination (stringReverse, stringToUpper)var res = toUpperAndReverse(str)
Copy the code

So when our requirements change, we don’t need to modify what we’ve packaged.

/ / 2

var str = 'function program'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split(' ').reverse().join(' ')}// var toUpperAndReverse = combination (stringReverse, stringToUpper)
// var res = toUpperAndReverse(str)

function stringToArray(str) {
    return str.split(' ')}varToUpperAndArray = combination (stringToArray, stringToUpper) toUpperAndArray(STR)Copy the code

As you can see, when changing the requirements, we didn’t break the previously packaged code, just added function functionality, and then regrouped the functions.

Here some people may say, need to modify, must change the code ah, you did not delete the previous code, is not considered to break the open and close principle. For the record, the open closed principle states that software entities such as classes, modules, and functions should be open for extension and closed for modification. It’s for the code that we’re encapsulating, abstracting out, not the logic that we’re calling. So this is not breaking the open close principle.

Suddenly the product had an Epiphany and wanted to change the requirements again, capitalizing the string, flipping it, and converting it to an array.

If you were thinking in the past and not abstracting, you would be in a state of mind, but if you were abstracting, you wouldn’t panic at all.

/ / 3

var str = 'function program'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split(' ').reverse().join(' ')}function stringToArray(str) {
    return str.split(' ')}varStrUpperAndReverseAndArray = (stringToArray stringReverse, stringToUpper) strUpperAndReverseAndArray (STR)Copy the code

It turns out that you haven’t changed the code you wrapped before, just the way the functions are combined. As you can see, composition is really about abstracting functions of a single function and then composing complex functions. This method not only exercises your abstraction ability, but also brings great maintenance convenience.

But I just use Chinese characters to replace the above combination, how should we achieve this combination? So the first thing we know is that this is a function, and the argument is a function, and the return value is a function.

For example 2, the composition of the two functions is composed. For example 2, the composition of the two functions is composed. For example 2, the composition of the two functions is composed.

function twoFuntionCompose(fn1, fn2) {
    return function() {
        // code}}Copy the code

Let’s think again about how we could compose the two functions in example 2 instead of composing, and whether we could do the same.

var res = stringReverse(stringToUpper(str))
Copy the code

For example, twoFuntonCompose is composed for twoFuntonCompose

function twoFuntonCompose(fn1, fn2) {
    return function(arg) {
        return fn1(fn2(arg))
    }
}
Copy the code

In the same way, we can write a combination of three functions, a combination of four functions, but always nested layers, become:

function multiFuntionCompose(fn1, fn2, .. , fnn) {
    return function(arg) {
        returnfnn(... (fn1(fn2(arg)))) } }Copy the code

This is obviously not what we programmers should do, but we can also see some rules, such as taking the return value of the previous function as an argument to the return value of the next, when the last function is directly returned.

So that’s what normal thinking would do.

function aCompose(. args) {
    let length = args.length
    let count = length - 1
    let result
    return function f1 (. arg1) {
        result = args[count].apply(this, arg1)
        if (count <= 0) {
          count = length - 1
          return result
        }
        count--
        return f1.call(null, result)
    }
}
Copy the code

So underscore is fine, but there are a lot of robust processing inside, and that’s kind of the core.

However, as a functional lover, I try to think in a functional way, so I use reduceRight to write the following code.

function compose(. args) {
    return (result) = > {
        return args.reduceRight((result, fn) = > {
          return fn(result)
        }, result)
  }
}
Copy the code

To compose realize there are a number of ways, of course, in this article compose five ideas also gives other imaginative way, before I read this article, three other I didn’t know, but feeling also is not very useful, but can expand our thoughts, interested students can have a look.

Note: There is a rule for passing the compose function to the compose function, which starts with the last argument and continues to the first one. There is also a requirement that the function passing the compose function to the compose function have only one parameter, and that the return value of the function must be an argument to the next function.

If you’re not comfortable with the way compose evaluates from the last function, you can use the pipe function to evaluate from left to right.

function pipe(. args) {
     return (result) = > {
        return args.reduce((result, fn) = > {
          return fn(result)
        }, result)
  }
}
Copy the code

The implementation is similar to compose, except that the traversal of parameters is reduced from right to left instead of right to left.

If you’ve read a lot of articles about how to implement compose, or currification, or partial application, etc., you probably don’t know what compose is for, and you haven’t used it, so you forget and forget again and again. After reading this article, I hope you can implement it easily. We’ll continue with the Currization and some of the applications.

point-free

In the world of functional programming, there is a very popular programming style. This style is also called Point-free and is referred to as parameter head, which roughly means the programming style without parameter.

// this is a parameter, because word is the parameter
var snakeCase = word= > word.toLowerCase().replace(/\s+/ig.'_');

// This is pointfree without any parameters
var snakeCase = compose(replace(/\s+/ig.'_'), toLowerCase);
Copy the code

The purpose of a parameter function is to get one data, while the purpose of a pointfree function is to get another function.

So what does pointfree do? It allows us to focus on functions, saves the hassle of naming arguments, and makes the code simpler and more elegant. It is important to note that a pointfree function may be composed of many non-Pointfree functions. This means that most of the underlying basic functions have arguments. Pointfree is represented by high-level functions composed of basic functions, which can be used as business functions. The business logic of our replication is constructed by combining different base functions.

Pointfree makes our programming more beautiful and declarative. This style is a kind of pursuit of functional programming, a standard. We can write pointfree as much as possible, but don’t overuse it.

In addition, the base function composed by compose is composed with only one parameter. However, the base function is composed with more than one parameter. In this case, a magic function (the Currification function) is used.

Currie,

Wikipedia defines currification as follows:

In computer science, Currying, also known as carryization or callization, is a technique of converting a function that takes multiple arguments into a function that takes a single argument (the first argument of the original function), and returning a new function that takes the remaining arguments and returns a result.

Get two important pieces of information from the definition:

  • Accept a single parameter
  • The return result is a function

For example, for composing a function that takes a single parameter, you can convert a function with multiple parameters to a function that takes a single parameter. For example, for composing a function that takes a single parameter, you can convert a function with multiple parameters to a function that takes a single parameter.

Currified functions allow us to pursue pointfree and make our code more elegant!

Let’s look at a specific example to understand currization:

Let’s say you own a store and you want to give your preferred customer a 10% discount:

function discount(price, discount) {
    return price * discount
}
Copy the code

When a discount customer buys a $500 item and you give him a discount:

const price = discount(500.0.10); / / $50
Copy the code

You can see us finding ourselves counting 10% discounts every day in the long run:

const price = discount(1500.0.10); / / $150
const price = discount(2000.0.10); / / $200
/ /... And so on many
Copy the code

We can currize the discount function so that we don’t have to add 0.10 discount every time.

// This is a currization function that converts the discount of two arguments to accept one argument at a time
function discountCurry(discount) {
    return (price) = > {
        returnprice * discount; }}const tenPercentDiscount = discountCurry(0.1);
Copy the code

Now, we can just count the price of the items your customers buy:

tenPercentDiscount(500); / / $50
Copy the code

Similarly, some prime customers are more important than others — let’s call them supercustomers. And we want to offer 20% discount to these super customers. We can use our Currified discount function:

const twentyPercentDiscount = discountCurry(0.2);
Copy the code

We configure a new function for our super customers by adjusting the Discount function to 0.2 (20%) from the Currified discount function. The function returned twentyPercentDiscount will be used to calculate our supercustomer discount:

twentyPercentDiscount(500); / / 100
Copy the code

I’m sure you’ve already gotten a feel for curiation from discountCurry ** above. This article is about how curiation works in functional programming, so let’s look at how it works in functions.

Now we have a requirement: given a string, flip, then uppercase, to see if there is TAOWENG, if so print yes, otherwise print no.

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split(' ').reverse().join(' ')}function find(str, targetStr) {
    return str.includes(targetStr)
}

function judge(is) {
    console.log(is ? 'yes' : 'no')}Copy the code

We can easily write these four functions, the first two of which are already mentioned above, and find is very simple. Now we want to implement pointfree in the compose mode, but our find function takes two parameters that do not comply with the compose parameter. Find (); find (); find (); find ();

// Currize find
function findCurry(targetStr) {
    return str= > str.includes(targetStr)
}

const findTaoweng = findCurry('TAOWENG')

const result = compose(judge, findTaoweng, stringReverse, stringToUpper)
Copy the code

See if you can see here that Currization is very useful in achieving pointfree, step-by-step implementation of our composition with fewer parameters.

However, the above way of currification needs to modify the previously encapsulated function, which also breaks the open and close principle, and for some basic functions to modify the source code, other places may have problems, so we should write a function to manually currification.

According to the previous definition of Currization and the previous two Currization functions, we can write a general currization function of two variables (with two arguments) :

function twoCurry(fn) {
    return function(firstArg) { // The first call gets the first argument
        return function(secondArg) { // The second call gets the second argument
            return fn(firstArg, secondArg) // Apply the two arguments to the function fn}}}Copy the code

So findCurry above can be obtained by twoCurry:

const findCurry = twoCurry(find)
Copy the code

This way we can leave the encapsulated functions unchanged, or we can use corrification and then combine the functions. But what we’ve done here is we’ve just implemented a two-component function, if it’s three, if it’s four we’re going to write a three-component Function, a four-component function, we can write a general n-component function.

function currying(fn, ... args) {
    if (args.length >= fn.length) {
        returnfn(... args) }return function (. args2) {
        returncurrying(fn, ... args, ... args2) } }Copy the code

When the number of parameters obtained is greater than or equal to the number of parameters of FN, it is proved that the parameters have been obtained, so fn is directly executed. If not, the parameters will continue to be obtained recursively.

It can be seen that the core idea of a general Currization function is very simple, and the code is very simple, and it also supports passing multiple parameters at the time of a call (but this passing multiple parameters is not very consistent with the definition of Currization, so it can be used as a variant of Currization).

I’m not focusing on the currization implementation here, so there are no more robust currization functions. How amazing is the currization of JavaScript functions.

Part of the application

A partial application is an operation that creates a smaller function by initializing a subset of the function’s immutable parameters to fixed values. In simple terms, if you have a function with five arguments, given three arguments, you get a function with one or two arguments.

If you look at the above definition, you might think that this is very similar to the Currization, which is used to shorten the length of function arguments, so if you understand the currization, it is very easy to understand the partial application:

function debug(type, firstArg, secondArg) {
    if(type === 'log') {
        console.log(firstArg, secondArg)
    } else if(type === 'info') {
        console.info(firstArg, secondArg)
    } else if(type === 'warn') {
        console.warn(firstArg, secondArg)
    } else {
        console.error(firstArg, secondArg)
    }
}

constLogDebug = Partial application (debug,'log')
constInfoDebug = Partial application (debug,'info')
constWarnDebug = Partial application (debug,'warn')
constErrDebug = Partial application (debug,'error')

logDebug('log:'.'Test part application')
infoDebug('info:'.'Test part application')
warnDebug('warn:'.'Test part application')
errDebug('error:'.'Test part application')
Copy the code

The debug method encapsulates the various methods used in debugging with console object. Originally, we need to pass three parameters. After we pass the encapsulation of part of the application, we only need to call different methods according to the need, and pass the necessary parameters.

This example might make you think that this encapsulation is unnecessary and doesn’t save any work, but if we debug not only to the console, but also to the database, or do something else, then this encapsulation would be useful.

As part of the application can also be cut parameters, he when we write the combination function also has its place, and parameters, can be faster delivery needs to be left to compose the parameters passed, here is with Mr Currie, because Mr Currie according to the definition of a function call can pass a parameter, if you have four or five parameters will need to:

function add(a, b, c, d) {
    return a + b + c +d
}

// Convert add to a unary function using the Currization method
let addPreThreeCurry = currying(add)(1) (2) (3)
addPreThree(4) / / 10
Copy the code

This kind of continuous call (here we are talking about Corrification by definition, not the corrification variant we wrote), but with partial applications:

// Use partial application to convert add to a unary function
constAddPreThreePartial = Partial application (add,1.2.3)
addPreThree(4) / / 10
Copy the code

Now that we know how to partially apply this function, let’s implement one. It’s really quite simple:

// The core implementation of a generic partial application function
function partial(fn, ... args) {
    return (. _arg) = > {
        returnfn(... args, ... _arg); }}Copy the code

If you haven’t noticed, this partial application is very similar to the bind function in JavaScript, in that it stores the parameters it first passes into the function through the closure, and passes the additional parameters to the function when it is called again, except that the partial application doesn’t specify this. So you can also use bind to implement a partial application function.

// The core implementation of a generic partial application function
function partial(fn, ... args) {
    return fn.bind(null. args) }Copy the code

It can also be seen that currization and partial application are actually very similar, so it is easy to confuse the two techniques. The main difference lies in the internal mechanism and control of parameter passing:

  • Currization generates nested unary functions on each distribution call. At the bottom level, the final result of a function is produced by a step-by-step combination of these unary functions. Meanwhile, Curry’s variant allows some arguments to be passed at the same time. Therefore, you can completely control when and how the function is evaluated.
  • Some applications bind (assign) the parameters of a function to some preset value, resulting in a new function with fewer parameters. The closure of the modified function contains these assigned parameters, which are fully evaluated in subsequent calls.

conclusion

In this article I would like to focus on functions to accomplish our requirements in a combinatorial manner, and also introduce a functional programming style: Pointfree, which gives us a best practice for functional programming, writes as pointfree as possible (if possible, not always), and then introduces currie or partial applications to reduce the number of function arguments required for compose or PIPE.

So the point of this kind of paper is to understand how we can combine functions, how we can abstract complex functions into smaller, single function functions. This will make our code more maintainable and declarative.

As for the other concepts mentioned in this article: closures, scopes, and other uses of Currization, I hope to understand them in more depth in my introductory article, but this article will focus on function composition.

Refer to the article

  • Pointfree and declarative programming for JavaScript functional programming
  • Understanding Currying in JavaScript
  • JavaScript Functional Programming Guide

The article was first published on taoyuan, my personal website, and can also be found on Github blog.

If you are interested, you can also follow my personal public account “Front-end Taoyuan”.