美文网首页
授之以渔

授之以渔

作者: 刀藏水 | 来源:发表于2020-08-08 20:29 被阅读0次

    今天谈谈函数式编程里面的函数第一公民的便利性。

    借用一下成龙大哥的霸王广告语: 其实老K刚开始安利函数式编程的时候我是很拒绝的,因为,你不能让我用,我就马上去用。 第一,我要试一下。我不想用完了以后,再配一个屁屁踢,项目咣的一下,说的很亮,很柔。这样同事一定会骂我,说我的代码看不懂,没有这样的写法。

    后来经过更长时间的尝试,理解。算是勉强理解了一些函数式编程的好处。 随着使用的越来越频繁,发现它的好处越来越多。今天花开n朵,只表一支。谈谈函数第一公民的特性是怎么样打开你编程新思路的。

    譬如有一个人想把大象装在冰箱里。他不会装,但是他知道你会,于是每次他要冷冻大象的时候就去找你,为此,他专门给你就留了一个座位,等你去的时候坐。终于,有一天你烦了。就把冷冻大象的步骤写在一个锦囊里,每次他找你的时候,你就把锦囊给他,告诉他随便找一个人就可以了。通过传递锦囊实现冰冻大象的方式,就是编程里面的function第一公民。

    上面絮絮叨叨的这一通,让本来自以为理解函数式编程的我竟然糊涂了。于是老老实实百度了一下定义,一下子又明白了。真是类比就是装逼,装逼害人害己。以后再也不类比(装逼)了。

    一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量。

    明白了吗?其实就是可以把函数当变量一样使用。 但是作为没有接触过函数式编程的人来说,他会问?我为什么要把函数当成变量一样用?有什么好处? 别着急,听我慢慢讲一下我做为一个编程小白是怎么样走上函数式编程的道路吧:

    我前几年曾经管理过一批重要的用户,我这样定义他们(实际情况比这个要复杂的多,会有更多的变量):

        @Data
        @AllArgsConstructor
        public static class User {
            private String name;
            private long fee;
            private int age;
            private int gender;
        }
    

    有一天, 产品经理(以下用x代称)对我说要把他们分三六九等。普通,乞丐贵宾,真贵宾。区分规则是根据他们交的费用。不交的是普通用户,一百以下的是乞丐贵宾,一百以上的是真贵宾,并让我把真贵宾筛选出来。

    easy! 我迅速写下代码:

        List<User> getVipUserList(List<User> list) {
            List<User> vipUserList = new ArrayList<>();
            for(User u : list) {
                if(u.getFee() >= 100) {
                    vipUserList.add(u);
                }
            }
            return vipUserList;
        }
    

    当我提交代码的时候,我顺便说了一句:“100块还真便宜啊!”。

    “是便宜,我们计划涨到149”,x说,“你改完了是吧?”

    “没,还差一点,快了,稍等”。

    我迅速把代码改成如下:

        List<User> getVipUserList(List<User> list, long fee) {
            List<User> vipUserList = new ArrayList<>();
            for(User u : list) {
                if(u.getFee() >= fee) {
                    vipUserList.add(u);
                }
            }
            return vipUserList;
        }
    

    这样的话,就可以根据不同的交费数额进行筛选。也算是能应对小小的变化了。

    过了几天, x又来了,说:“把年龄小于30岁的人筛选出来”

    “没问题!”, 我写下了如下代码:

    
        List<User> getYoungUserList(List<User> list, int age) {
            List<User> yountUserList = new ArrayList<>();
            for(User u : list) {
                if(u.getAge() <= age) {
                    yountUserList.add(u);
                }
            }
            return yountUserList;
        }
    

    又过了几天, x又来了,说“把男性用户筛选出来”
    “好的!“

        List<User> getMaleOrFemaleUserList(List<User> list, int gender) {
            List<User> userList = new ArrayList<>();
            for(User u : list) {
                if(u.getGender() == gender) {
                    userList.add(u);
                }
            }
            return userList;
        }
    
    

    又过了几天,x说,“统计一下30岁以下的vip会员发我”

    “OK”

        List<User> getYountVipUserList(List<User> list, int age, long fee) {
            List<User> youngVipUserList = new ArrayList<>();
            for(User u : lst) {
                if(u.getAge() <= age && u.getFee() >= fee) {
                    youngVipUserList.add(u);
                }
            }
            return youngVipUserList;
        }
    

    “对了, 你能把30岁以上的女vip会员发我吗?”

    “好吧”

        List<User> getYountVipUserList(List<User> list, int age, long fee, int gender) {
            List<User> youngVipUserList = new ArrayList<>();
            for(User u : list) {
                if(u.getAge() <= age && u.getFee() >= fee && u.getGender() == gender) {
                    youngVipUserList.add(u);
                }
            }
            return youngVipUserList;
        }
    

    生活就这样一天天过去。有一次夜深人静,我开始考虑我写的代码和砖头的区别。并担忧是不是以后就这样干这种低效,重复的工作了。有没有更高效率更有意思的编程方法呢?

    上面写的这几个方法,大部分的结构都是相似的,只有判断逻辑是不一样的, 是否可以把这块判断逻辑“拆”出来, 就像当时拆出fee字段那样?这时老K自信狡诈的笑容再一次出现在脑海。“函数式编程!”。把判断逻辑当成变量传进去,那上面的这些方法就可以压缩成这样了

        List<User> filterUserList(List<User> list, function f) {
            List<User> userList = new ArrayList<>();
            for(User u : list) {
                if(f(u)) {
                    userList.add(u);
                }
            }
            return userList;
        }
    
    

    java里面没有function, 不过可以用函数式接口代替function, 再配上lambda表达式。想到这, 我写了如下代码:

        List<User> filterUserList(List<User> list, Predicate<User> predicate) {
            List<User> userList = new ArrayList<>();
            for(User u : list) {
                if(predicate.test(u)) {
                    userList.add(u);
                }
            }
            return userList;
        }
    

    如查想筛选所有的年龄大于50岁的人, 我就可以这样写了

            List<User> list = f.filterUserList(allUser, x -> x.getAge() > 50);
    

    一行代码解决了问题!爽!

    不过还有一个新的问题,如果是多个条件怎么办?就像要取“30岁以后的女vip会员”?

    “那就把所有的判断条件装入数组! 对!”。我又新加了一个如下的方法:

        List<User> filterUserList(List<User> list, Predicate<User>... predicate) {
            List<User> userList = new ArrayList<>();
            for(User u : list) {
                for(Predicate p : predicate) {
                    if(!p.test(u)) {
                        break;
                    }
                }
                userList.add(u);
            }
            return userList;
        }
    
    
    
    

    如果想取“30岁以后的女vip会员”, 就可以这样写:

            List<User> list2 = f.filterUserList(allUser,
                    x -> x.getAge() < 30,
                    x -> x.getGender() == 0,
                    x -> x.getFee() >= 150
            );
    

    程序写成这样,对自己有一些满意了,回头想了想函数第一公民的定义。“一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量”。 作为函数参数和赋值给变量这个都理解了,那做为函数返回值呢? 做为函数返回值的意思就是返回值是一个函数, 可以执行。放在java里面就是返回值是一个function interface。那是不是可以这样,先用一个参数,List<User>,调用一个方法, 这个方法返回一个Function,返回的这个Function接收一个“判断逻辑”, 这样的话,代码是不是就更简单了呢?想到这里, 我赶紧试了一下:

        Function<Predicate<User>, List<User>> filter(List<User> list) {
            return predicate -> {
                List<User> userList = new ArrayList<>();
                for(User u : list) {
                    if(predicate.test(u)) {
                        userList.add(u);
                    }
                }
                return userList;
            };
        }
    

    当我要筛选的时候:

        Function<Predicate<User>, List<User>> filter = obj.filter(list);
        List<User> ageGreater30 = filter.apply(x -> x.getAge() >= 30);
        List<User> maleVip = filter.apply(x -> x.getAge() >= 30 && x.getGender() == 1);
    

    看到代码变成现在这样。 感觉真不可思议。一种编程方式的改变,外观上带来的变化就这么大。而且,貌似,好像,低级程序员再也不能随便改我的代码了。看来有一天我也有可能达到老K的高度。想到这, 我甜甜的进入了梦
    乡。。。

    注:本文其实涉及到一些函数式编程里面的概念,像“高阶函数”,“柯里化”,感兴趣的读者可以找一些相关资料系统学习。

    文章里面的代码可以访问  代码的github地址

    如果认为我写的文章不错,可以添加我的微信公众号,我会每周发一篇原创文章,和大家共同探讨编程,学习编程。


    刀藏水

    相关文章

      网友评论

          本文标题:授之以渔

          本文链接:https://www.haomeiwen.com/subject/qyqfrktx.html