共计 6505 个字符,预计需要花费 17 分钟才能阅读完成。
介绍
而函数式编程和申明式编程是有所关联的,因为他们思维是统一的:即只关注做什么而不是怎么做。但函数式编程不仅仅局限于申明式编程。
函数式编程是面向数学的形象,将计算形容为一种表达式求值,其实,函数式程序就是一个表达式。
一、函数式编程实质
函数式编程中的函数并部署指计算机中的函数,而是指数学中的函数,即自变量的映射。函数的值取决于函数的参数的值,不依赖于其余状态,比方 abs(x)函数计算 x 的绝对值,只有 x 不变,无论何时调用、调用次数,最终的值都是一样。
二、函数式编程的特点
一、函数是第一等公民
所谓 ” 第一等公民 ”(first class),指的是函数与其余数据类型一样,处于平等位置,能够赋值给其余变量,也能够作为参数,传入另一个函数,或者作为别的函数的返回值。
def func(data:str)->String;
return data
def run(func:Function, data:str)-> void;
return func(data)
二、函数是纯函数(只用 ” 表达式 ”,不必 ” 语句 ”)
@FunctionalInterface
public interface Consumer<E> {E getData();
}
Consumer<String> consumer3 = ()-> str + "consumer2"; // 简化
System.out.println(consumer1.getData());
“ 表达式 ”(expression)是一个单纯的运算过程,总是有返回值;” 语句 ”(statement)是执行某种操作,没有返回值。函数式编程要求,只应用表达式,不应用语句。也就是说,每一步都是单纯的运算,而且都有返回值。起因是函数式编程的开发动机,一开始就是为了解决运算(computation),不思考零碎的读写(I/O)。” 语句 ” 属于对系统的读写操作,所以就被排挤在外。当然,理论利用中,不做 I / O 是不可能的。因而,编程过程中,函数式编程只要求把 I / O 限度到最小,不要有不必要的读写行为,放弃计算过程的单纯性。
函数编程语言最重要的根底是 λ 演算(lambda calculus),而且 λ 演算的函数能够承受函数当作输出(参数)和输入(返回值)。
三、没有 ” 副作用 ”
function func(data){
let num = 1;
return function(){return data + num;}
}
所谓 ” 副作用 ”(side effect),指的是函数外部与内部互动(最典型的状况,就是批改全局变量的值),产生运算以外的其余后果,比方在下面中 func 中包装了 2 层的命名空间 。
函数式编程强调没有 ” 副作用 ”,意味着函数要放弃独立,所有性能就是返回一个新的值,没有其余行为,尤其是不得批改内部变量的值。
在其余类型的语言中,变量往往用来保留 ” 状态 ”(state)。不批改变量,意味着状态不能保留在变量中。函数式编程应用参数保留状态,最好的例子就是递归。
四、援用透明性
函数程序通常还增强援用透明性,即如果提供同样的输出,那么函数总是返回同样的后果。就是说,表达式的值不依赖于能够扭转值的全局状态。这使您能够从模式上推断程序行为,因为表达式的意义只取决于其子表达式而不是计算程序或者其余表达式的副作用。这有助于验证正确性、简化算法,甚至有助于找出优化它的办法。
副作用副作用是批改零碎状态的语言构造。因为 FP 语言不蕴含任何赋值语句,变量值一旦被指派就永远不会扭转。而且,调用函数只会计算出后果 ── 不会呈现其余成果。因而,FP 语言没有副作用。
长处:
五、靠近自然语言,易于了解
函数式编程的自由度很高,能够写出很靠近自然语言的代码。
比方 java 中的 stream 流解决
Stream.of("fo", "bar", "hello").map(func).forEach(str->{System.out.println(str);
});
因而,函数式编程的代码更容易了解。
六、易于 ” 并发编程 ”
函数式编程不须要思考 ” 死锁 ”(deadlock),因为它不批改变量,所以基本不存在 ” 锁 ” 线程的问题。不用放心一个线程的数据,被另一个线程批改,所以能够很释怀地把工作摊派到多个线程,部署 ” 并发编程 ”(concurrency)。
三、函数式编程的理论利用
一、lambda 表达式
lambda 表达式的语法格局如下:
应用 “->” 将参数和实现逻辑拆散;
() 中的局部是须要传入 Lambda 体中的参数;
{} 中局部,接管来自 () 中的参数,实现肯定的性能。
大部分语言如下
(parameters) -> expression
或
(parameters) ->{statements;}
python 如下
lambda 关键字申明这个一个表达式 : 后面的 x 为承受的参数,:前面的是表达式,能够承受任意多个参数
>>> lam = lambda x:x+3
>>> n2 = []
>>> for i in numbers:
... n2.append(lam(i))
...
以下是 lambda 表达式的重要特色:
- 可选类型申明:不须要申明参数类型,编译器能够对立辨认参数值。
-
可选的参数圆括号:一个参数无需定义圆括号,但多个参数须要定义圆括号。
- 可选的大括号:如果主体蕴含了一个语句,就不须要应用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会主动返回值,大括号须要指定明表达式返回了一个数值。
–
## 二、理论利用
@FunctionalInterface
public interface Consumer<E> {E getData();
}
Consumer<String> consumer1 = new Consumer<String>() { // 传统
@Override
public String getData() {return str + "consumer1";}
};
Consumer<String> consumer2 = () -> { // jdk8
return str + "consumer2";
};
Consumer<String> consumer3 = ()-> str + "consumer2"; // 简化
System.out.println(consumer1.getData());
System.out.println(consumer2.getData());
System.out.println(consumer3.getData());
在函数式接口的开发中,加上 @FunctionalInterface 注解检测该接口只有一个办法,在调用中能力应用()-> 向上转型过程中辨认该接口
public static void main(String[] args) {List<String> arr = Arrays.asList(new String[]{"1","2","3"});
Function<String, String> func = getFunc(arr);
System.out.println("***************************");
Stream.of("fo", "bar", "hello").map(func).forEach(str->{System.out.println(str);
});
}
public static Function<String, String> getFunc(Collection<String> items) {System.out.println("开始");
Iterator<String> li = items.iterator();
return new Function<String, String>() {
@Override
public String apply(String str) {return str + li.next();
}
};
// return (item)-> item + li.next(); // 下面的简写}
在下面的例子中,咱们把函数封装为一个对象,这个对象返回了一个函数的援用,等到函数被调用时产生向上转型,这时,这个返回的函数才正式被调用
执行程序是:
当程序执行,到第三行时 Function<String, String> func = getFunc(arr); 时
getFunc 被执行,这时 11 行和 12 行曾经执行,数据暂存到内存中,返回一个函数的援用,到第五行时
stream 生成蕴含 3 个元素的程序数据流,在 map 中依然进行了一次函数式接口的调用,返回了一个无状态数据流(返回由利用给定值的后果组成的流函数指向该流的元素。),在返回的 StatelsessOp 类中
opWrapSink 中仍旧如此,直到 accept 开始对数据进行生产,而后开始 forEach 调用,Function<String,
String> 中的第二个 String 示意返回的数据类型,因为下面 map 中返回的数据流还未被生产,直到 str->
System.out.println(str); 正式被生产,str 代表 map 中返回的数据类型
@param <E_IN> type of elements in the upstream source
- @param <E_OUT> type of elements in produced by this stage
上面是 map 的源码
javascript
function add(val){
let num = val;
return function(){if (num <10){
num++;
return function(){return num;}
}
return num;
}
}
三、根本运算
一)、函数合成(compose)
如果一个值要通过多个函数,能力变成另外一个值,就能够把所有两头步骤合并成一个函数,这叫做 ” 函数的合成 ”(compose)。
指的是将代表各个动作的多个函数合并成一个函数。
上图中,X 和 Y 之间的变形关系是函数 f,Y 和之间的变形关系是函数 g,那么 X 和 Z 之间的关系,就是 g 和 f 的合成函数 g·f。
下面讲到,函数式编程是对过程的形象,关注的是动作。看下上面的例子
function add(x) {return x + 10}
function multiply(x) {return x * 10}
console.log(multiply(add(2))) // 120
// 将合成的动作形象为一个函数 compose 如下:const compose = function(f,g) {return function(x) {return f(g(x));
};
}
let calculate=compose(multiply,add);
console.log(calculate(2)) // 120
二)、柯里化用处
当初须要实现一个性能,将一个全是数字的数组中的数字转换成百分数的模式。依照失常的逻辑,咱们能够按如下代码实现
function getPercentList(array) {return array.map(function(item) {return item * 100 + '%'})
}
console.log(getPercentList([1, 0.2, 3, 0.4]));
// 后果:['100%', '20%', '300%', '40%']
如果通过柯里化的形式来实现
function map(func, array) {return array.map(func);
}
var mapCurry = createCurry(map);
var getNewArray = mapCurry(function(item) {return item * 100 + '%'})
console.log(getPercentList([1, 0.2, 3, 0.4]));
高阶函数
满足下列条件之一的函数就能够称为高阶函数:
- 函数作为参数被传递
- 把函数当作参数传递,这代表咱们能够抽离出一部分容易变动的业务逻辑,把这部分业务逻辑放在函数参数中,这样一来能够拆散业务代码中变动与不变的局部。其中一个重要利用场景就是常见的回调函数。
上面例子中 js 的函数都是对高阶函数的利用:
[1, 4, 2, 5, 0].sort((a, b) => a - b);
// [0, 1, 2, 4, 5]
[0, 1, 2, 3, 4].map(v => v + 1);
// [1, 2, 3, 4, 5]
[0, 1, 2, 3, 4].every(v => v < 5);
// true
函数作为返回值输入
让函数持续返回一个可执行的函数,意味着运算过程是可连续的。
const fn = (() => {let students = [];
return {addStudent(name) {if (students.includes(name)) {return false;}
students.push(name);
},
showStudent(name) {if (Object.is(students.length, 0)) {return false;}
return students.join(",");
}
}
})();
fn.addStudent("python");
fn.addStudent("java");
fn.showStudent(); // 输入:python,java
一个函数 2 个办法体
同时满足两个条件的高阶函数
const plus = (...args) => {
let n = 0;
for (let i = 0; i < args.length; i++) {n += args[i];
}
return n;
}
const mult = (...args) => {
let n = 1;
for (let i = 0; i < args.length; i++) {n *= args[i];
}
return n;
}
const createFn = (fn) => {let obj = {};
return (...args) => {let keyName = args.join("");
if (keyName in obj) {return obj[keyName];
}
obj[keyName] = fn.apply(null, args);
return obj[keyName];
}
}
let fun1 = createFn(plus);
console.log(fun1(2, 2, 2)); // 输入:6
let fun2 = createFn(mult);
console.log(fun2(2, 2, 2)); // 输入:8
python 版
def func():
num1 = 1
def add(num):
num += num1
return num
def del_(num):
num -= num1
return num
return add, del_
print(func()[0](10))
print(func()[1](10))
public static Function<String, String> getFunc(Collection<String> items, Boolean isFunc) {System.out.println("开始");
Iterator<String> li = items.iterator();
if(isFunc){return new Function<String, String>() {
@Override
public String apply(String str) {return str + li.next();
}
};
}
return (item)-> item + li.next();}
函数式编程的递归和迭代
迭代
function add(val){
let num = val;
return function(){if (num <10){
num++;
return function(){return num;}
}
return num;
}
}
每次调用 num 加一
递归版:
function add(val){
let num = val;
return function add2 (){if (num <10){
num++;
const run = add2();
return num;
}
return num;
}
}
https://zhuanlan.zhihu.com/p/…
http://ruanyifeng.com/blog/20…
https://www.baidu,com
材料来自下面两个文档,还有很多小白本人写的 demo,大佬真的讲得很细,作为学渣不敢谈话