Currization and partial application of functions

Some terms

01 Unary Function

const identity = (x) = > x;
Copy the code

02 Function of two variables

const add = (x,y) = > x + y;
Copy the code

03 Variable parameter function

function variadic(a, ... variadic){
    console.log(a)
    console.log(variadic)
}
variadic(1.2.3);
/ / 1
/ / [2, 3]
Copy the code

Currie,

Currization is the process of converting a multi-parameter function into a nested unary function

const add = (x,y) = > x + y;
Copy the code

Currie’s plate

const addCurried = x= > y => x + y;
addCurried(4) (4)
/ / 8
Copy the code

Here you manually convert the two parameters to an addCurried function with nested unary functions. Let’s transform this process into a method called Curry

const curry = (binaryFn) = > {
    return function (firstArg){
        return function (secondArg){
            return binaryFn(firstArg, secondArg)
        }
    }
}
Copy the code

Let autoCurrieAdd = curry(add) autoCurriedAdd(2)(2)

01 Chemical use cases

001 a function to create a list

const tableOf2 = (y) = > 2 * y
const tableOf3 = (y) = > 3 * y
const tableOf4 = (y) = > 4 * y
Copy the code
tableOf2(4)
/ / 8
tableOf3(4)
/ / 12
tableOf4(4)
/ / 16
Copy the code

Currified table function

const genericTable = (x,y) = > x * y;

const tableOf2 = curry(genericTable)(2) 
const tableOf3 = curry(genericTable)(3) 
const tableOf4 = curry(genericTable)(4) 

tableOf2(2)
/ / 4
tableOf3(2)
/ / 6
tableOf4(2)
/ / 8
Copy the code

The 002 review of curry

We know that we can only currize one function, but what about multiple functions? Let’s add a rule

let curry = (fn) = > {
    if(typeoffn ! = ='function') {throw Error('No function provided')}}Copy the code

With this layer of checking, if someone else calls the Curry function with an integer like 2, they get an error! The next requirement for currization is that if someone provides all the parameters for a Currization function, the actual function needs to be executed by passing those parameters

let curry = (fn) = > {
    // No function provided
    return function curriedFn(. args){
        return fn.apply(null, args)
    }
}
Copy the code

CurriedFn is the variator function that returns the result of passing args and calling the function through apply

const multiply = (x,y,z) = > x * y * z
curry(multiply)(1.2.3)  Multiply (1,2,3)
/ / 6
curry(multiply)(1.2.0)
/ / 0
Copy the code

003 The following is the problem of converting a multi-parameter function to a nested unary function (this is the definition of The Coriolization).

let curry = (fn) = > {
    if(typeoffn ! = ='function') {throw Error('No function provided')}return function curriedFn(. args){
        if(args.length < fn.length){
            return function(){
                return curriedFn.apply(null, args.concat([].slice.call(arguments)))}}return fn.apply(null, args)
    }
}
Copy the code

Args. Length

Return curriedFn. Apply (null, args.concat([].slice.call(arguments)))

This fragment: args.concat([].slice.call(arguments)) is very important. We pass in the parameters one at a time and recursively call curriedFn to temporarily store the parameters in arGS.

The jF (args.length

Return fn.apply(null, args)

This will produce the complete result of the function

curry(multipiy)(3) (2) (1)
/ / 6
Copy the code

004 Log function

const loggerHelper = (mode, initialMessage, errorMessage, lineNo) = > {
    if(mode === 'DEBUG') {console.debug(initialMessage, errorMessage, lineNo)
    }else if(mode === 'ERROR'){
        onsole.error(initialMessage, errorMessage, lineNo)
    }else if(mode === 'WARN'){
        onsole.warn(initialMessage, errorMessage, lineNo)
    }else{
        throw "Wrong node"}}Copy the code

Developers typically use functions as follows

loggerHelper("ERROR"."Error at stats.js"."Invalid argument passed".23)
Copy the code

Curry solves the problem of reusing the first two arguments:

let errorLogger = curry(loggerHelper)('ERROR') ("Error at Stats.js")
let debugLogger = curry(loggerHelper)('DEBUG') ("Debug at Stats.js")
let warnLogger = curry(loggerHelper)('WARN') ("Warn at Stats.js")
Copy the code

It is now easy to reference the Above Currified functions and use them in their respective contexts

// ...
errorLogger("Error message".21)
// ...
debugLogger("Debug message".45)
// ...
warnLogger("Warn message".33)
Copy the code

005 lodash curry

import {curry} from 'lodash'

var match = curry((reg, str) = > str.match(reg))
var filter = curry((f, arr) = > arr.filter(f))
var haveSpace = match(/\s+/g);
// haveSpace('ffffffff')
// haveSpace('a b')

// filter(haveSpace, ["abced", "Hello world"])
filter(haveSpace)(["abocd"."Hello world"])
Copy the code

In fact, currification is a way of “preloading” a function, passing fewer arguments and getting a new function that has already remembered those arguments. In a sense, it is a caching of arguments, which is a very efficient way of writing functions

02 Currization Finds numbers in an array

let match = curry(function(expr, str) {
    return str.match(expr)
})
let hasNumber = match(/ [0-9] + /)
// Then create a filter function
let filter = curry(function(f, ary){
    return ary.filter(f)
})
// With hasNumber and filter, we can create a new function called findNumbersInArray
let findNumbersInArray = filter(hasNumber)
// Now test it
findNumbersInArray(['js'.'number1'])
// ["number1"]
Copy the code

The data flow

Kerrization always accepts an array at the end. This is intentional!

Partial application

setTimeout((a)= > console.log("Do X tast"), 10)
setTimeout((a)= > console.log("Do Y tast"), 10)
Copy the code

Each setTimeout is passed 10. Can you hide it in your code? Can you use the Curry function? The answer is no. The reason is that the Curry function applies the argument list from the left to the right! An alternative is to encapsulate the setTimeout function

const setTimeoutWrapper = (time, fn) = > {
    setTimeout(fn, time)
}

const delayTenMs = curry(setTimeoutWrapper)(10)
delayTenMs((a)= > console.log("Do X task"))
delayTenMs((a)= > console.log("Do Y task"))
Copy the code

The problem is that we have to create a wrapper like setTimeoutWrapper, which is an overhead! And this is where partial applications can be used!

Implementation bias

Partial application of function definitions

const partial = function (fn, ... partialArgs){
    let args = partialArgs;
    return function(. fullArguments){
        let arg = 0;
        for(let i=0; i<args.length && arg < fullArguments.length; i++){
            if(args[i] === undefined){ args[i] = fullArguments[arg++]; }}return fn.apply(null, args); }}Copy the code
let delayTanMs = partial(setTimeout, undefined.10);
delayTenMs((a)= > console.log("Do Y task"));
Copy the code

This outputs the expected result. Let’s walk through the partial details. The first time we catch the parameters of the function passed in:

partial(setTimeout, undefined.10)
// This will generate
let args = partialArgs
// args = [undefined, 10]
Copy the code

The returned function will remember the value of args (yes, closure here). The returned function takes a parameter called fullArguments. So, like delayTenMs, you can call it with a passing argument:

delayTenMs((a)= > console.log("Do Y task"))
/ / fullArguments pointing
// [()=> console.log("Do Y task")]

// Args that use closures will contain
// args = [undefined, 10]
Copy the code

In the for loop we iterate and create the required array of parameters for the function:

if(args[i] === undefined){
    args[i] = fullArguments[arg++];
}
Copy the code

So let’s start at I equals zero

// args = [undefined, 10]
// fullArguments = [()=> console.log("Do Y task")]
args[0] = >undefined= = =undefined // true

// Inside the if loop
args[0] = fullArguments[0]
// args[0] = ()=> console.log("Do Y task")

// So args will become
// [()=> console.log("Do Y task"), 10]
Copy the code

Args points to the array we expect for the setTimeout function call. Once we have the required parameters in args, we can call the function via fn.apply(null,args).

We can apply partial to any function that has multiple arguments.

let obj = {foo:"bar".bar:"foo"}
JSON.stringify(obj, null.2)
Copy the code

The last two arguments to a stringify call are always the same as “null,2”. You can remove boilerplate code with partial

let prettyPrintJson = partial(JSON.stringify, undefined.null.2)
prettyPrintJson({foo:"bar".bar: "foo"})
Copy the code

We learned about two techniques. So the question is which one to use when? The answer depends on how the API is defined. If the API were defined like map/filter, we could easily use the Curry function to solve the problem. But it often backfires. Like setTimeout, the most appropriate choice is a partial function!

Combination of two functions and pipes

Symbol | is known as pipe symbol. It allows us to combine functions to create a new function that solves the problem! Roughly speaking | send the left-most function output as input to the right-most function! Technically, this process is called piping

In a word: The output of each program should be the input of another unknown program. We combine a new function from the basic function: each basic function takes an argument and returns data

Looking back at apressBooks, our solution is to get objects from apressBooks that have the title and Author fields and are rated higher than 4.5

map(filter(apressBooks, (book) => book.rating[0] > 4.5), (book) => {
    return {title: book.title, auhor: book.author}
})
Copy the code

We obtained the following results

[{title: 'c#'.author: "ANDREW TROELSEN"}]Copy the code

Here we combine the two functions by sending the output of one function as input to the other

Compose function

const compose = (a,b) = > (c) => a(b(c))
Copy the code

The compose function first executes b and passes the return value of B to A as an argument. The direction of the function call is from right to left.

Application compose function

Example 1 evaluates a given number to a round

No combination method is used

let data = parseFloat("3.56")
let number = Math.round(data)
/ / 4
Copy the code

Let’s solve the problem with the compose function

let number = compose(Math.round, parseFloat)
// Equivalent to number = (c) => math.round (parseFloat(c))
number(3.67)
/ / 4
Copy the code

That’s the combination of functions!

Example 2: Count the number of words in a string

let splitIntoSpaces = (str) = > str.split("");
let count = (array) = > array.length;

const countWords = compose(count, splitIntoSpaces)
Copy the code
countWords("hello your reading about composition")
/ / 5
Copy the code

Example 3 introduces curry and partial

We can combine two functions only if the function takes one argument. But there are also multi-parameter functions. You can then do this with a curry or partial function.

Review map and Filter

map(filter(apressBooks, (book) => book.rating[0] > 4.5), (book) => {
    return {title: book.title, auhor: book.author}
})
Copy the code

Both the map and filter functions take two arguments: the first is an array, and the second is a function that operates on the array. So you can’t just combine them.

However, we can resort to partial functions, assuming that we define many small functions in the code base to filter books based on different ratings

let filterOutStandingBooks = (book) = > book.rating[0= = =5;
let filterGoodBooks = (book) = > book.rating[0] > 4.5;
let filterBadBooks = (book) = > book.rating[0] < 3.5;
Copy the code

There are many projection functions

let projectTitleAndAuthor = (book) = > {
    return {
        title: book.title,
        author: book.author
    }
}
let projectAuthor = (book) = > {
    return { author: book.author }
}
let projectTitle = (book) = > {
    return {title: book.title
}
Copy the code

The idea of combinatorial functions is to combine small functions into one large function. Simple functions are easy to read, test, and maintain.

let queryGoodBooks = partial(filter, undefined, filterGoodBooks);
let mapTitleAndAuthor = partial(map, undefined, projectTitleAndAuthor);
let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor, queryGoodBooks)
Copy the code

Now let’s see how Compose combines the map and filter for two parameters

partial(filter, undefined, filterGoodBooks);
partial(map, undefined, projectTitleAndAuthor);
Copy the code

Both of these partial applications accept only one array parameter! With these two partial functions, you can compose them

let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor, queryGoodBooks)
Copy the code

Now function titleAndAuthorForGoodBooks accepts only one parameter, the following pass apressbooks object array to it:

titleAndAuthorForGoodBooks(apressBooks)
Copy the code

Example 4. Combine multiple functions

The current version of the compose function can only combine two given functions. How do you combine three, four or more functions? Let’s rewrite the compose function

Remember: we need to send the output of each function as input to another function (by recursively storing the output of the last function executed). You can use the reduce function

const compose = (. fns) = > 
    (value) => 
        reduce(fns.reverse(), (acc, fn) => fn(acc), value)
Copy the code

Reverse () reverses the array of functions and passes in the function (acc,fn)=>fn(acc), which calls each function in turn with the acc passed in as its argument. Obviously: the initial value of the accumulator is the value variable, which will be the first input to the function

Let’s test by counting the number of words for a given string

let splitIntoSpaces = (str) = > str.split("");
let count = (array) = > array.length;
const countWords = compose(count, splitIntoSpaces);

// Count the words
countWords("Hello your reading about composition")
/ / 5

Copy the code

Suppose we want the number of words in a given string to be odd or even.

let oddOrEven = (ip) = > ip % 2= =0 ? "even" : "odd"
Copy the code

Combine these three functions with the new compose function

const oddOrEvenWords = compose(oddOrEven, count, splitIntoSpaces);
oddOrEvenWords("Hello your reading about composition")
// ['odd']
Copy the code

Five Point Free

  • Turn some of your object’s native methods into pure functions, and don’t name transient intermediate variables.
  • In this function, we use STR as our intermediate variable, but this intermediate variable is meaningless except to make the code a bit longer
const f = str= > str.toUpperCase().split(' ')
Copy the code
var toUpperCase = word= > word.toUpperCase()
var split = x= > (str= > str.split(x))

var f = compose(split(' '), toUpperCase)

f("abcd efgh")
Copy the code
  • This style helps reduce unnecessary naming and keeps the code simple and generic.

Lazy evaluation, lazy function

  • In an instruction language, the following code is executed in order of each function, which must be executed in order because each function may change or depend on external state
function ajax(){
    var xhr = null;
    if(widow.XMLHttpRequrest){
        xhr = new XMLHttpRequrest()
    }else{
        xhr = new ActiveXObject('Microsoft.XMLHTTP')
    }
    ajax = xhr;
}
Copy the code

Tail-call optimization

The last action inside a function is a function call. The return value of this call is returned directly to the function. The function calls itself, called recursion. If the tail calls itself, it is called tail recursion. Recursion requires saving a large number of call records and is prone to stack overflow errors. If you use tail recursion optimization to turn recursion into a loop, you only need to save one call record and no stack overflow errors will occur.

Traditional recursion, not recursion, can’t optimize

function factorial(n){
    if(n === 1) return 1;
    return n * factorial(n -1)
}

Copy the code

ES6 enforces tail recursion

function factorial(n, total){
    if(n === 1) return total;
    return factoria(n -1, n * total)
}
Copy the code

Browsers do not support this, use while instead

function fn()
var i = 10;
while(i--){
    fn()
}
Copy the code

Category and container

  1. We may think of the category as a container containing two things. Value, value of the deformation relationship, that is, the function.
  2. Category theory uses functions to express the relationships between categories.
  3. With the development of category theory, a whole set of function operation methods appear. This method was originally used for mathematical operations, but it was later implemented by computer students and became what is known today as functional programming
  4. In essence, functional programming is the same kind of mathematical method as mathematical logic, calculus, and determinant. Why does functional programming require functions to be pure and free of side effects? Because it’s a mathematical operation, the original purpose is to evaluate, do nothing else, otherwise you wouldn’t be able to satisfy the functional algorithm.
  5. Functions can be used not only to convert values within the same category, but also to convert one category into another. This involves the Functor
  6. Functors are the most important data types in functional programming, as well as the basic units of operation and function. It is first and foremost a category, that is, a container containing values and deformation relations. In particular, its deformation relationship can be applied to each value in turn, forming the current container into another container.

  • $(..) The object returned is not a native DOM object, but rather a wrapper around the native object, which is in a sense a “container” (but not functional)
  • Functor A container type that complies with certain rules
  • Functor is an abstraction of a function call; we give the container the ability to call the function itself. When we map a function, we let the container run the function itself. This gives the container the freedom to choose how and when to operate the function, resulting in lazy evaluation, error handling, asynchronous calls, and so on.