函数式编程:一等函数(First-class Function)
说起函数式编程,不得不提的是First-class Function
的概念,有些文章把它翻译成“第一类函数”,有些是“一等函数”,我更倾向于“一等函数”,因为这种名字更能体现出“一等数据类型”的概念。那么什么是一等函数呢?它指的是带有最少限制的函数,它们的“权利或者特权包括”:
- 可以用变量命名
- 可以提供给过程作为参数
- 可以由过程作为结果返回
- 可以包含在数据结构中
这样的函数就跟字符串一样,可以传来传去,也就成了一种“数据”。接下来分别介绍这四个特征的一些应用。(本文示例以最常见的Javascript为主,但也会给出Java的示例,作为强类型语言的参考)。
特征一:可以用变量命名
首先来看一个js的例子
function hello(){
console.log("hello")
}
let funHello = hello
funHello();
这里定义了一个函数hello
,然后将其和变量funHello
绑定,然后对变量funHello
进行函数调用,结果就是hello
函数被调用。
可以用变量命名这点非常重要,因为变量可以作为参数传递,可以用来接收过程调用的返回值,也可以作为例如json对象、结构体的属性,所以函数也可以被用来干这些事。例如:
当然在js里还可以这样编写:
let funHello1 = function (){
console.log("hello")
}
let funHello2 = () => {
console.log("hello")
}
但是到Java这边,由于Java是强类型语言,而Java实现上并没有对一等函数的直接支持,所以只能用Java 8的函数式接口来实现这样的表达:
public class FunMain {
static void hello(){
System.out.println("hello");
}
public static void main(String[] args) {
Runnable r = FunMain::hello;
r.run();
Runnable r1 = () -> {
System.out.println("hello");
};
r1.run();
}
}
特征二:可以提供给过程作为参数
这点应用就比较广泛了,各种高阶函数,例如回调函数:
function testCallback(callback){
console.log("do something....")
callback()
}
testCallback(() => {
console.log("complete...")
})
另外还有一个经典例子,比如,首先我们编写一个过滤出一个数组中偶数的数字:
function filterEven(nums){
let result = []
for(let i = 0;i < nums.length;i++){
if(nums[i] %2 == 0){
result.push(nums[i])
}
}
return result;
}
filterEven([1,2,3,4,5,6]) // => [2,4,6]
然后来了个新的功能,要编写一个过滤出1-10之间数:
function filter1to10(nums){
let result = []
for(let i = 0;i < nums.length;i++){
if(nums[i] >= 1 && nums[i] <= 10){
result.push(nums[i])
}
}
return result;
}
filter1to10([1,22,3,44,5,66]) //=> [1,3,5]
其实大家会发现代码结构非常类似,只有判断条件不同,我们将其提取成参数后:
function filter(datas, predicte){
let result = []
for(let i = 0;i < datas.length;i++){
if(predicte(datas[i])){
result.push(datas[i])
}
}
return result;
}
filter([1,2,3,4,5,6], i => i % 2 == 0)
filter([1,22,3,44,5,66], i => i >= 1 && i <= 10)
这样代码的复用性就得到了很大提升,下面要过滤不同类型的数值,只需要传入不同的条件就可以了。
Java版本的如下:
public class FunMain {
public static void main(String[] args) {
List<Integer> list1 = filterInts(Arrays.asList(1, 2, 3, 4, 5), i -> i % 2 == 0);
}
public static List<Integer> filterInts(List<Integer> list, Predicate<? super Integer> predicate) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
Integer data = list.get(i);
if (predicate.test(data)) {
result.add(data);
}
}
return result;
}
}
还可以利用泛型做进一步优化:
public class FunMain {
public static void main(String[] args) {
List<Integer> list1 = filter(Arrays.asList(1, 22, 3, 44, 5,66), i -> i >= 1 && i <= 10);
}
public static <T> List<T> filter(List<T> list, Predicate<? super T> predicate) {
List<T> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
T data = list.get(i);
if (predicate.test(data)) {
result.add(data);
}
}
return result;
}
}
public class FunMain {
public static void main(String[] args) {
List<Integer> list1 = filter(Arrays.asList(1, 22, 3, 44, 5,66), i -> i >= 1 && i <= 10);
}
public static <T> List<T> filter(List<T> list, Predicate<? super T> predicate) {
List<T> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
T data = list.get(i);
if (predicate.test(data)) {
result.add(data);
}
}
return result;
}
}
特征三:可以由过程作为结果返回
这一点说得通俗点就是返回函数的函数,或者说函数生成器。例如,我们考虑下面这样的一个加法器:
function adder(n){
return i => {
return i + n
}
}
let add3 = adder(3);
add3(4) // => 7
add3(10) // => 13
这里函数adder
接受一个数字n
,返回一个将参数加n
的函数。
对应Java版本为:
public class FunMain {
public static void main(String[] args) {
Function<Integer, Integer> add3 = adder(3);
int r = add3.apply(4);// => 7
System.out.println(r);
}
static Function<Integer, Integer> adder(int n){
return i -> {
return i + n;
};
}
}
我们还可以编写反函数的生成器:
function complement(f){
return n => !f(n)
}
function odd(n){
return n % 2 != 0;
}
let even = complement(odd)
odd(1)
even(2)
对应的Java版本为:
public class FunMain {
public static void main(String[] args) {
Predicate<Integer> odd = i -> i % 2 != 0;
Predicate<Integer> even = complement(odd);
System.out.println(odd.test(1) + " - " + even.test(2));
}
static <T> Predicate<T> complement(Predicate<T> predicate) {
return t -> !predicate.test(t);
}
}
另外,我们还可以和特征二结合起来实现函数组合的效果:
function compose (f1, f2){
return i => f2(f1(i))
}
function square (n){
return n * n
}
function triple(n){
return 3 * n
}
let squareAndTriple = compose(square, triple);
squareAndTriple(4) // => 48
这里我们利用compose
把两个单参函数square
和triple
组合,得到一个新的函数squareAndTriple
。对应Java版本为:
public class FunMain {
public static void main(String[] args) {
Function<Integer, Integer> squareAndTriple = compose(FunMain::square, FunMain::triple);
Integer r = squareAndTriple.apply(4);
System.out.println(r);
}
static int square(int n) {
return n * n;
}
static int triple(int n) {
return 3 * n;
}
static <T, K, R> Function<T, R> compose(Function<T, K> f1, Function<K, R> f2) {
return t -> f2.apply(f1.apply(t));
}
}
特征四:可以包含在数据结构中
这一点意味着,函数可以作为集合的元素,也可以作为对象的属性等等,例如,我们可以得到一个函数序列:
function identity(i){
return i
}
let funList = [
i => i * i,
function (i){
return i + 1
},
identity
]
for(let i = 0;i < funList.length;i++){
console.log(funList[i](10))
}
对应Java版本为:
public class FunMain {
public static void main(String[] args) {
List<Function<Integer, Integer>> list = Arrays.asList(
i -> i * i,
i -> i + 1,
FunMain::identity
);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).apply(10));
}
}
static int identity(int n) {
return n;
}
}