函数式编程之名词解释

函数式编程名词众多,可能有些我们在日常的编码中实践过,但并不知道他的学名,也有可能很多我们从来没有听说过,下面是一些常见的函数式编程中的名词说明

  • 纯函数 Purity
  • 函数柯里化 Currying
  • 偏函数 Partial Application
  • 惰性函数 Lazy evaluation
  • 函数组合 Function Composition
  • 高阶函数 Higher-Order Functions
  • 函数记忆 Memoize
  • 函数副作用 Side effects

纯函数 Purity

与外界交换数据只有唯一渠道——参数和返回值,并且函数的执行不会对外部环境产生副作用

var arr = [1,2,3,4,5];

// Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的
// 可以,这很函数式
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]


// Array.splice是不纯的,它有副作用,对于固定的输入,输出不是固定的
// 这不函数式
xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []

函数副作用 Side effects

如果函数或表达式与其自身作用域之外的可变数据发生了读写操作,那么此时函数和表达式就产生了副作用

let greeting;
const greet = () => greeting = "Hi, " + window.name;
// greet() 执行时更改了外部环境的变量
greet();
// => "Hi, Brianne"
// new Date() 是可变数据
const differentEveryTime = new Date();
// 这里表示系统接收到的输入值是不确定的,是一种可变数据
console.log("IO is a side effect!");

偏函数 Partial Application

原函数的基础上预填充部分参数并返回的新函数

// 下面是一个创建偏函数的辅助函数
const partial = (f, ...args) => (...moreArgs) => f(...args, ...moreArgs);
const add3 = (a, b, c) => a + b + c;
// 预填充 (add3, 2, 3) 三个参数,空置最后一个参数,返回一个新的函数
const fivePlus = partial(add3, 2, 3); // (c) => 2 + 3 + c
fivePlus(4);
// => 9

惰性求值 Lazy evaluation

惰性求值,是一种按需执行的求值策略,只有需要某个值时才会执行相关的表达式。在函数式编程语言中,这一特性可用于构造无限列表。

const rand = function*() {
while (true) {
yield Math.random();
}
}
const randIter = rand();
randIter.next().value;
// 每次执行 next() 函数都会返回一个新的随机数
// 有且只有在执行 next() 的时候才会返回新值

函数柯里化 Currying

将一个接收多个参数的函数转化为单参数函数的方式
将接收多个参数的函数转换为一系列只接收一个参数的函数的过程

//比较容易读懂的ES5写法
var add = function(x){
return function(y){
return x + y
}
}

//ES6写法,也是比较正统的函数式写法
var add = x => (y => x + y);

//试试看
var add2 = add(2);
var add200 = add(200);

add2(2); // =>4
add200(50); // =>250

函数组合 Function Composition

接收多个函数作为参数并返回一个新函数的方式

const add = function ( x, y ) {
return x + y;
};

const square = function ( x ) {
return x * x;
};

const composeTwo = function ( f, g ) {
return function ( x, y ) {
return g( f ( x, y ) );
};
};

const addThenSquare = composeTwo( add, square );

高阶函数 Higher-Order Functions

函数接收一个或多个函数做为参数,并返回一个函数

const numbers = [1, 2, 3];

const print = function ( input ) {
console.log( input );
};

numbers.forEach( print );

函数记忆 Memoize

得益于纯函数的特性,相同的参数可能返回相同的结果,那么就可以缓存参数对对应的结果,以减少反复的计算,从而提高性能


var squareNumber = memoize(function(x){ return x*x; });

squareNumber(4);
//=> 16

squareNumber(4); // 从缓存中读取输入值为 4 的结果
//=> 16


var memoize = function(f) {
var cache = {};

return function() {
var arg_str = JSON.stringify(arguments);
cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
return cache[arg_str];
};
};
var memoize = function(func, hasher) {
var memoize = function(key) {
var cache = memoize.cache;
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
if (!cache[address]) {
cache[address] = func.apply(this, arguments);
}
return cache[address];
};
memoize.cache = {};
return memoize;
};


var count = 0;
var fibonacci = function(n){
count++;
return n < 2? n : fibonacci(n-1) + fibonacci(n-2);
};
for (var i = 0; i <= 10; i++){
fibonacci(i)
}

console.log(count) // 453


var count = 0;
var fibonacci = function(n) {
count++;
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};

fibonacci = memoize(fibonacci)

for (var i = 0; i <= 10; i++) {
fibonacci(i)
}

console.log(count) // 12

pointfree style

point-free style 是一种不显式向函数传递参数的代码风格,通常需要柯里化和高阶函数来实现

// 需求:输入 'kevin',返回 'HELLO, KEVIN'。

// 非 pointfree,因为提到了数据:name
var greet = function(name) {
return ('hello ' + name).toUpperCase();
}

// pointfree
// 先定义基本运算,这些可以封装起来复用
var toUpperCase = function(x) { return x.toUpperCase(); };
var hello = function(x) { return 'HELLO, ' + x; };

var greet = compose(hello, toUpperCase);
greet('kevin');