函数式编程:如何高效简洁地对数据查询与变换

摘要:一提到编程范式,很容易联想到宗教的虔诚,每种宗教所表达信条都有必定合理性,但若是一直只遵循一种教条,可能也被让本身痛苦不堪,编程范式也是如此。

案例1

案例一,代码摘抄来自一企业培训材料,主要代码逻辑是打印每课成绩,并找出学生非F级别课程统计平均分数:算法

class CourseGrade {
 public String title;
 public char grade;
}

public class ReportCard {
 public String studentName;
 public ArrayList<CourseGrade> cliens;

 public void printReport() {
        System.out.println("Report card for " + studentName);
        System.out.println("------------------------");
        System.out.println("Course Title       Grade");
        Iterator<CourseGrade> grades = cliens.iterator();
        CourseGrade grade;
 double avg = 0.0d;
 while (grades.hasNext()) {
            grade = grades.next();
            System.out.println(grade.title + "    " + grade.grade);
 if (!(grade.grade == 'F')) {
                avg = avg + grade.grade - 64;
            }
        }
        avg = avg / cliens.size();
        System.out.println("------------------------");
        System.out.println("Grade Point Average = " + avg);
    }
}

上面的代码有哪些问题呢:编程

  • 成员变量采用public,缺乏数据封装性
  • 没有判断cliens是否为空,可能除以0值。注:假定它不会为空,另外逻辑可能有问题,为何统计总分是非F课程,除数倒是全部课程Size,先忽略这个问题
  • avg这个变量多个用途,便是总分,又是平均分
  • cliens变量名难以理解
  • !(grade.grade == 'F') 有点反直觉
  • while循环干了两件事,打印每课的成绩,也统计了分数

培训材料并未给标准解题,尝试优化一下代码,采用Java8的Stream来简化计算过程,并对代码进行了分段:segmentfault

public void printReport2() {
        System.out.println("Report card for " + studentName);
        System.out.println("------------------------");

        System.out.println("Course Title       Grade");
        cliens.forEach(it -> System.out.println(it.title + "    " + it.grade));

 double total = cliens.stream().filter(it -> it.grade != 'F')
                .mapToDouble(it -> it.grade - 64).sum();
        System.out.println("------------------------");
        System.out.println("Grade Point Average = "  + total / cliens.size());
    }

进一步优化,把各种打印抽取各自函数:设计模式

private void printHeader() {
        System.out.println("Report card for " + studentName);
        System.out.println("------------------------");   
    }

 private void printGrade() {
        System.out.println("Course Title       Grade");
        cliens.forEach(it -> System.out.println(it.title + "    " + it.grade));
    }

 private void printAverage() {
 double total = cliens.stream().filter(it -> it.grade != 'F')
                .mapToDouble(it -> it.grade - 64).sum();
        System.out.println("------------------------");
        System.out.println("Grade Point Average = "  + total / cliens.size());
    }

 public void printReport3() {
        printHeader();
        printGrade();
        printAverage();
    }

注:若是只算非F的平均分,能够一行搞定:闭包

double avg = cliens.stream().filter(it -> it.grade != 'F').mapToDouble(it -> it.grade - 64).average().orElse(0.0d);

案例二:再看一段代码:

List<Integer> tanscationsIds = transcations.parallelStream()
        .filter(it -> it.getType() == Transcation.GROCERY)
        .sorted(comparing(Transcation::getValue).resersed())
        .map(Transcation::getId)
        .collect(Collectors::toList());

代码很是清晰:编程语言

  • 过滤出类型为GROCERY的交易记录
  • 按其value值进行倒排序
  • 各自取其Id字段
  • 输出Id列表

这看起来是否是像这样一条SQL语句:select t.id from tanscations t where t.type == 'GROCERY' order by t.value desc函数式编程

1 背后的知识

目前Java8已普遍使用,对于Stream与Lambda应习觉得常了,而再也不是一种炫技。网上也有很是多的教程,如有同窗还不熟悉他们的用法,能够多找找材料熟悉一下。函数

Stream正如其名,像一条数据生产流水线,逐步叠加中间操做(算法和计算),把数据源转换为另外一个数据集。工具

笔者很早之前学过C#,接触过LINQ(Language Integrated Query),它比Java的Stream和Lambda用法更为清晰简洁,先给个简单示例:学习

var result = db.ProScheme.OrderByDescending(p => p.rpId).Where(p => p.rpId > 10).ToList();

LINQ为数据查询而生,能够算是DSL(Domain Specific Language)了,背后也是函数式编程(FP)一套理念,先记住其中两点:

  • Monad 是一种设计模式,表示将一个运算过程,经过函数拆解成互相链接的多个步骤
  • Lambda表达式 是一个匿名函数,Lambda表达式基于数学中的λ演算得名

FP还有其它的特性:模式匹配,柯里化,偏函数,闭包,尾递归等。对FP感受兴趣的同窗不妨找找材料学习一下。

如今的主流语言,都引入一些FP特性来提高语言在数据上的表达能力。

C++11引入Lambda表达式,并提供<algorithm>,<functional>两个基础库,一个简单示例:

int foo[] = { 10, 20, 5, 15, 25 };
std::sort(foo, foo+5, [](int a,int b){return a > b;});

Python提供functools库来简化一些函数式编程(仍是至关的弱),一个简单示例:

foo = ["A", "a", "b", "B"]
sorted(foo, key=functools.cmp_to_key(locale.strcoll))

2 函数式编程

固然,面向对象语言中增长lambda这类特征不能就称为函数式编程了,大部分只不过是语法糖。是采用什么编程范式不在于语言的语法,而是在于思惟方式。

面向对象编程(OOP)在过去20多年很是成功,而函数式编程(FP)也不断地发展,他们相生相息,各自解决不一样的场景问题:

  • 面向对象能够理解为是对数据的抽象,好比把一个事物抽象成一个对象,关注的是数据。
  • 函数式编程是一种过程抽象的思惟,就是对当前的动做去进行抽象,关注的是动做。

现实业务需求每每体现为业务活动,它是面向过程的,即先输入数据源,在必定条件下,进行一系列的交互,再输出结果。那面向过程与函数式的的区别是什么:

  • 面向过程能够理解是把作事情的动做进行分解多个步骤,因此有if/while这类语法支撑,走不一样的分支步骤。
  • 函数式相比面向过程式,它更加地强调执行结果而非执行过程,利用若干个简单的执行单元让计算结果不断渐近,逐层推导复杂的运算,而不是像面向过程设计出复杂的执行过程,因此纯函数式编程语言中不须要if/while这类语法,而是模式匹配,递归调用等。

面向对象的编程经过封装可变的部分来构造可以让人读懂的代码,函数式编程则是经过最大程度地减小可变的部分来构造出可以让人读懂的代码。

咱们从Java的Stream实现也看到函数式的另外一个特色:

  • 函数不维护任何状态,上下文的数据是不变的,传入的参数据处理完成以后再扔出来。

结合上面的理解,咱们能够先把世界事物经过OOP抽象为对象,再把事物间的联系与交互经过FP抽象为执行单元,这种结合或许是对业务活动的实现一种较好的解决方式。

3 避免单一范式

一提到编程范式,很容易联想到宗教的虔诚,每种宗教所表达信条都有必定合理性,但若是一直只遵循一种教条,可能也被让本身痛苦不堪。编程范式也是如此,正如Java在1.8以前是纯面向对象式,你就会以为它很是繁琐。也如Erlang是纯函数式,你就会发现有时简单的逻辑处理会很是复杂。

近些年来,因为数据分析、科学计算和并行计算的兴起,让人认识到函数式编程解决数据领域的魅力,它也愈来愈受欢迎。在这些领域,程序每每比较容易用数据表达式来表达,采用函数式能够用不多代码来实现。

现实的业务软件,不少的逻辑其实也是对数据的处理,最简单是对数据的CURD,以及数据的组合、过滤与查询。因此函数式编程在许多语言中都获得支持,提高了对数据处理的表达能力。

了解新的编程范式在适当的时候使用它们,这会使你事半功倍。不管什么编程范式,他们都是工具,在你的工具箱中,可能有锤子,螺丝刀…,这个工具在何时使用,取决待解决的问题。

4 结语

本文的案例只是一个引子,主要是想给你带来函数式编程的一些理念,函数式给咱们解决业务问题提供了另外一种思惟方式:如何高效简洁地对数据查询与变换。许多语言都支持函数式一些能力,须要咱们不断地学习,在合理的场景下使用他们。

本文分享自华为云社区《飞哥讲代码16:函数式让数据处理更简洁》,原文做者:华为云专家。

点击关注,第一时间了解华为云新鲜技术~

相关文章
相关标签/搜索