• 函数式编程:一等函数(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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里定义了一个函数hello,然后将其和变量funHello绑定,然后对变量funHello进行函数调用,结果就是hello函数被调用。

    可以用变量命名这点非常重要,因为变量可以作为参数传递,可以用来接收过程调用的返回值,也可以作为例如json对象、结构体的属性,所以函数也可以被用来干这些事。例如:

    
    
    • 1

    当然在js里还可以这样编写:

    let funHello1 = function (){
        console.log("hello")
    }
    
    let funHello2 = () => {
        console.log("hello")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    但是到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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    特征二:可以提供给过程作为参数

    这点应用就比较广泛了,各种高阶函数,例如回调函数:

    function testCallback(callback){
        console.log("do something....")
        callback()
    }
    
    testCallback(() => {
        console.log("complete...")
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    另外还有一个经典例子,比如,首先我们编写一个过滤出一个数组中偶数的数字:

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后来了个新的功能,要编写一个过滤出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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其实大家会发现代码结构非常类似,只有判断条件不同,我们将其提取成参数后:

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样代码的复用性就得到了很大提升,下面要过滤不同类型的数值,只需要传入不同的条件就可以了。

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    还可以利用泛型做进一步优化:

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    特征三:可以由过程作为结果返回

    这一点说得通俗点就是返回函数的函数,或者说函数生成器。例如,我们考虑下面这样的一个加法器:

    function adder(n){
        return i => {
            return i + n
        }
    }
    
    let add3 = adder(3);
    add3(4)  // => 7
    add3(10) // => 13
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里函数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;
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们还可以编写反函数的生成器:

    function complement(f){
        return n => !f(n)
    }
    
    function odd(n){
        return n % 2 != 0;
    }
    
    let even = complement(odd)
    odd(1)
    even(2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    对应的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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    另外,我们还可以和特征二结合起来实现函数组合的效果:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这里我们利用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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    特征四:可以包含在数据结构中

    这一点意味着,函数可以作为集合的元素,也可以作为对象的属性等等,例如,我们可以得到一个函数序列:

    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))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    对应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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    Git下载安装及基本配置
    上海华清071班
    关于#微信#的问题:win11想强制删除微信在系统托盘的图标,求帮助(操作系统-windows)
    C++初阶:C++入门
    fseek()函数 和 ftell()函数
    Ioc容器加载过程-bean生命周期源码解析
    00后干一年跳槽就20K,测试老油条表示真怕被这个“卷王”干掉····
    JavaWeb-解析Http协议
    guacamole安装
    深入浅出PyTorch函数torch.rand与torch.randn
  • 原文地址:https://blog.csdn.net/zssrxt/article/details/126570359