函数式编程:一等函数(First-class Function)

函数式编程:一等函数(First-class Function)

说起函数式编程,不得不提的是First-class Function的概念,有些文章把它翻译成“第一类函数”,有些是“一等函数”,我更倾向于“一等函数”,因为这种名字更能体现出“一等数据类型”的概念。那么什么是一等函数呢?它指的是带有最少限制的函数,它们的“权利或者特权包括”:

  1. 可以用变量命名
  2. 可以提供给过程作为参数
  3. 可以由过程作为结果返回
  4. 可以包含在数据结构中

这样的函数就跟字符串一样,可以传来传去,也就成了一种“数据”。接下来分别介绍这四个特征的一些应用。(本文示例以最常见的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把两个单参函数squaretriple组合,得到一个新的函数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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值