JavaScript 中的函数式编程原理

Doing some research, I found functional programming concepts like immutability and pure functions. Those concepts enable you to build side-effect-free functions, so it is easier to maintain systems -- with some other benefits.
(通过一些调查,我发现函数式编程的概念像是不可变以及纯函数方法。这些概念能使你建立没有副作用的函数,因此很容易去管理系统,还有一些好处)

what is functional programming?

Function programming is a programming paradigm - a style of building the structure and elements of computer programs - that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data -- Wikipedia.

how do we know if a function is pure or not? Here is a very strict definition od purity(怎么去判断一个函数是不是纯函数):

  • It return the same result if given the same arguments(it is also referred as deterministic)(同样的输入得到同样的输出-确定性)
  • It does not cause any observable side effects(不会引起任何明显的副作用)

It returns the same result if given the same arguments

let PI = 3.14;

const calculateArea = (radius) => radius * radius * PI;

calculateArea(10); // returns 314.0

Observation: mutability is discouraged in functional programming.(可变性在函数式编程中是不鼓励的)

let counter = 1;

function increaseCounter(value) {
  counter = value + 1;
}

increaseCounter(counter);
console.log(counter); // 2

How would we make it pure?(怎么使他编程存函数)

let counter = 1;

function increaseCounter(value) {
  counter = value + 1;
}

increaseCounter(counter);
console.log(counter); // 2

Pure functions are stable, consistent, and predictable. Given the same parameters, pure functions will always return the same result. We don't need to think of situations when the same parameter has different results - because it will never happen.(纯函数是稳定的,唯一的和可预测的,给与相同的参数,纯函数将总是返回相同的结果。我们不需要考虑当参数相同,结果不相同的场景,因为这绝对不会发生)

pure functions benefits(纯函数的好处)

The code's definitely easier to test.(该代码绝对易于测试)

let list = [1, 2, 3, 4, 5];

const incrementNumbers = (list) => list.map(number => number + 1);

incrementNumbers(list); // [2, 3, 4, 5, 6]

Immutability

Unchanging over time or unable to be changed.

Wen data is immutable, its state cannot change after it's created. If you want to change an immutable object, you can't. instead, you create a new object with the new value.(当数据是不可变的,在创建的时候,他的状态是不能变化,你绝不能改变一个不可变的对象,你可以用新值创建一个新对象)

var values = [1, 2, 3, 4, 5];
var sumOfValues = 0;

for (var i = 0; i < values.length; i++) {
  sumOfValues += values[I];
}

sumOfValues // 15

How do we handle mutability in iteration? Recursion.(我们如何处理迭代中的可变性?递归。)

let list = [1,2,3,4,5]
let accumulator = 0;

function sum(list, accumulator) {
  if(!list.length) {
    return accumulator;
  }
  return sum(list.splice(1), accumulator + list[0])
}

sum(list, accumulator); // 15
list; // [1, 2, 3, 4, 5]
accumulator; // 0

Observation: We can use reduce to implement this function.(我们能用 reduce 来实现这个函数)

let list = [1,2,3,4,5]
let accumulator = 0;

function sum(list, accumulator) {
  return list.reduce((accumulator_Q, current) => {
    return accumulator_Q + current
  })
}

sum(list, accumulator); // 15
list; // [1, 2, 3, 4, 5]
accumulator; // 0
const string = " I will be a url slug   ";

const slugify = string =>
  string
    .toLowerCase()
    .trim()
    .split(" ")
    .join("-");

slugify(string); // i-will-be-a-url-slug

Referential transparency(参照透明)

const sum = (a, b) => a + b;

sum(3, sum(5, 8));

functions as first-class entities (函数是一等公民)

Function as first-class entities can:

  • refer to it from constants and variables(从常量和变量中引用它)
  • pass it as a parameter to other functions(作为参数传递给其他函数)
  • return it as result from other functions(从其他函数作为结果返回)

This idea is treat functions as values and pass functions like data. This way we can combine different functions to create new functions with new behavior.(这个想法是将函数视为值,并将函数作为数据传递。这样,我们可以组合不同的功能来创建具有新行为的新功能。)

const sum = (a, b) => a + b;
const subtraction = (a, b) => a - b;

const doubleOperator = (f, a, b) => f(a, b) * 2;

doubleOperator(sum, 3, 1); // 8
doubleOperator(subtraction, 3, 1); // 4

filter

Syntax

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

Parameters
  • callback: Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise. It accepts three arguments:
    • element: The current element being processed in the array.
    • index | Optional: The index of the current element being processed in the array.
    • array | Optional: The array filter was called upon.
  • thisArg | Optional: Value to use as this when executing callback.
var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenNumbers = [];

for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 == 0) {
    evenNumbers.push(numbers[i]);
  }
}

console.log(evenNumbers); // (6) [0, 2, 4, 6, 8, 10]

const even = num => num % 2 === 0
const listOfNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
listOfNumbers.filter(even)

var filterArray = function(x, coll) {
  var resultArray = [];

  for (var i = 0; i < coll.length; i++) {
    if (coll[i] < x) {
      resultArray.push(coll[i]);
    }
  }

  return resultArray;
}

console.log(filterArray(3, [10, 9, 8, 2, 7, 5, 1, 3, 0])); // (3) [2, 1, 0]

function smaller(number) {
  return number < this;
}

function filterArray(x, listOfNumbers) {
  return listOfNumbers.filter(smaller, x);
}

let numbers = [10, 9, 8, 2, 7, 5, 1, 3, 0];

filterArray(3, numbers);

map

The map method transforms a collection by applying a function to all of its elements and building a new collections from the returned values.(map方法通过将函数应用于其所有元素并根据返回的值构建新的集合来转换集合。)

var people = [
  { name: "TK", age: 26 },
  { name: "Kaio", age: 10 },
  { name: "Kazumi", age: 30 }
];

var peopleSentences = [];

for (var i = 0; i < people.length; i++) {
  var sentence = people[i].name + " is " + people[i].age + " years old";
  peopleSentences.push(sentence);
}

console.log(peopleSentences); // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']
const makeSentence = (person) => `${person.name} is ${person.age} years old`;

const peopleSentences = (people) => people.map(makeSentence);
  
peopleSentences(people);
// ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']

var values = [1, 2, 3, -4, 5];

for (var i = 0; i < values.length; i++) {
  values[i] = Math.abs(values[i]);
}

console.log(values); // [1, 2, 3, 4, 5]
let values = [1, 2, 3, -4, 5];

const updateListMap = (values) => values.map(Math.abs);

updateListMap(values); // [1, 2, 3, 4, 5]

reduce

The idea of reduce is to receive a function and a collection, and return a value created by combining the items.(reduce的想法是接收一个函数和一个集合,并返回通过组合项目创建的值。)

Syntax

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

Parameters
  • callback: A function to execute on each element in the array (except for the first, if no initialValue is supplied), taking four arguments(在数组中的每个元素上执行的函数(第一个元素除外,如果未提供initialValue的话),带有四个参数:):
    • accumulator: The accumulator accumulates the callback's return values. It is the accumulated value previously returned in the last invocation of the callback, or initialValue, if supplied (see below).
    • currentValue: The current element being processed in the array.
    • index | Optional: The index of the current element being processed in the array. Starts from index 0 if an initialValue is provided. Otherwise, starts from index 1.
    • array | Optional: The array reduce() was called upon.
  • initialValue | Optional: A value to use as the first argument to the first call of the callback. If no initialValue is supplied, the first element in the array will be used and skipped. Calling reduce() on an empty array without an initialValue will throw a TypeError.
var orders = [
  { productTitle: "Product 1", amount: 10 },
  { productTitle: "Product 2", amount: 30 },
  { productTitle: "Product 3", amount: 20 },
  { productTitle: "Product 4", amount: 60 }
];

var totalAmount = 0;

for (var i = 0; i < orders.length; i++) {
  totalAmount += orders[i].amount;
}

console.log(totalAmount); // 120
let shoppingCart = [
  { productTitle: "Product 1", amount: 10 },
  { productTitle: "Product 2", amount: 30 },
  { productTitle: "Product 3", amount: 20 },
  { productTitle: "Product 4", amount: 60 }
];

const sumAmount = (currentTotalAmount, order) => currentTotalAmount + order.amount;

const getTotalAmount = (shoppingCart) => shoppingCart.reduce(sumAmount, 0);

getTotalAmount(shoppingCart); // 120

Another way to get the total amount is to compose map and reduce. What do I mean by that? We can use map to transform the shoppingCart into a collection of amount values, and then just use the reduce function with sumAmount function.(获得总量的另一种方法是组合 map 并进行 reduce。那是什么意思我们可以使用map 将 shoppingCart 转换为金额值的集合,然后仅将 reduce 函数与 sumAmount 函数一起使用。)


const getAmount = (order) => order.amount;
const sumAmount = (acc, amount) => acc + amount;

function getTotalAmount(shoppingCart) {
  return shoppingCart
    .map(getAmount)
    .reduce(sumAmount, 0);
}

getTotalAmount(shoppingCart); // 120

参考文档:
Functional Programming Principles in Javascript

你可能感兴趣的:(JavaScript 中的函数式编程原理)