摘要:使用Scala语言为例,展示函数式编程消除重复无聊的foreach代码。

难度:中级

概述###

大多数开发者在开发生涯里,会面对大量业务代码。而这些业务代码中,会发现有大量重复无聊的 foreach 循环,有时是为了获取对象的一个关键字段的值,有时是为了设置对象的某些字段的值,有时是为了转换得到另外一个对象,有时是为了增加若干新的字段。主要有如下情况:

  • map origin object to new object in order to get new list or new map ; 将一个对象映射为另一个对象,得到一个新的列表;
  • filter some objects to get new list according to condition function; 根据某个条件函数,过滤出所需要的对象列表;
  • if-add, if-remove, if-set ; 在满足某种条件的情况下, 设置对象的某些字段的值, 为对象动态增加若干字段、从列表中直接移除对象;
  • 聚合操作。在满足某种条件的情况下,抽取所指定对象的某些字段的值并进行聚合操作。聚合操作比如求和、最大值、合并等。

注意到 filter 和 if-remove 的区别。 一般来说, filter 会返回一个全新的不可变列表,拥有并发安全性,会有若干空间开销,只要列表不是特别大,都可以选用; 而 if-remove 则会直接从原列表中移除元素,导致列表可变, 不拥有并发安全,节省若干空间开销,适合于列表很大的情况。

实际上,这些foreach 代码完全可以使用函数式编程来消除重复一遍遍地写 foreach , 而专注于遍历里需要做的操作和业务逻辑。

代码示例###

以下显示了Scala函数式编程如何消除业务层的foreach代码。

  1. object Sex extends Enumeration {
  2. val Female = Value("Female")
  3. val Male = Value("Male")
  4. val Double = Value("Double")
  5. }
  6. class Person(var name:String, var age:Int, var ables:List[String], val sex:Sex.Value) {
  7. def setAge(age:Int):Unit = {
  8. this.age = age
  9. }
  10. def empty():String = { return "" }
  11. def getValue(fieldName:String):Any = {
  12. fieldName match {
  13. case "name" => name
  14. case "age" => age
  15. case "ables" => ables
  16. case "sex" => sex
  17. case _ => empty
  18. }
  19. }
  20. override def toString = {
  21. s"${this.name} is ${this.sex} sex , ${this.age} years old, able to do : " + this.ables.mkString("'",",", "'")
  22. }
  23. }
  24. object PersonsVisitor {
  25. /**
  26. * Map Processing Pattern
  27. * Fetch part fields from object list.
  28. */
  29. def getField(persons:List[Person], fieldName:String):List[Any] = {
  30. persons.map(p => p.getValue(fieldName))
  31. }
  32. /**
  33. * Filter Processing Pattern
  34. * Get some objects satisfy condition function from object list
  35. */
  36. def filter(persons:List[Person], accept:(Person => Boolean)): List[Person] = {
  37. persons.filter(accept)
  38. }
  39. /**
  40. * Aggregation Operation Pattern, eg. concat, sum
  41. */
  42. def aggregate[T](persons:List[Person], op: (Person => T), aggre: (List[T] => T)): T = {
  43. aggre(persons.map(op))
  44. }
  45. /**
  46. * If-Set Operation Pattern
  47. */
  48. def ifSet(persons:List[Person], accept:(Person=>Boolean), setFunc: (Person=>Unit)): List[Person] = {
  49. persons.foreach { p => if (accept(p)) { setFunc(p) } }
  50. persons
  51. }
  52. /**
  53. * If-Remove Operation Pattern
  54. */
  55. def ifRemove(persons:List[Person], accept:(Person=>Boolean)):Iterator[Person] = {
  56. persons.iterator.filter(p => ! accept(p) )
  57. }
  58. def buildPersons():List[Person] = {
  59. List( new Person("Lier", 20, List("Study", "Explore","Combination"), Sex.Male),
  60. new Person("lover", 16, List("Love","Chat","Combination"), Sex.Female),
  61. new Person("Tender", 18, List("Care", "Combination"), Sex.Double))
  62. }
  63. }
  64. object NoRepeatForeach extends App {
  65. launch()
  66. def launch():Unit = {
  67. val persons = PersonsVisitor.buildPersons()
  68. println(PersonsVisitor.getField(persons, "name"))
  69. println(PersonsVisitor.getField(persons, "ables"))
  70. println(PersonsVisitor.getField(persons, "sex"))
  71. println(PersonsVisitor.getField(persons, "none"))
  72. PersonsVisitor.filter(persons, p => p.ables.contains("Care")).foreach { println _ }
  73. println("All ables: " + PersonsVisitor.aggregate(persons, p=>p.ables.mkString(","), (ablelist:List[String]) => ablelist.toSet.mkString(",")))
  74. println("Total age: " + PersonsVisitor.aggregate(persons, p=>p.age, (agelist:List[Int]) => agelist.sum))
  75. println("If-Set:" + PersonsVisitor.ifSet(persons, p=> p.age >= 18, p=> p.setAge(p.age+1) ))
  76. println("If-Remove: " + PersonsVisitor.ifRemove(persons, p=> p.age >= 18).toList)
  77. }
  78. }

输出如下:

  1. List(Lier, lover, Tender)
  2. List(List(Study, Explore, Combination), List(Love, Chat, Combination), List(Care, Combination))
  3. List(Male, Female, Double)
  4. List(, , )
  5. Tender is Double sex , 18 years old, able to do : 'Care,Combination'
  6. All ables: Study,Explore,Combination,Love,Chat,Combination,Care,Combination
  7. Total age: 54
  8. If-Set:List(Lier is Male sex , 21 years old, able to do : 'Study,Explore,Combination', lover is Female sex , 16 years old, able to do : 'Love,Chat,Combination', Tender is Double sex , 19 years old, able to do : 'Care,Combination')
  9. If-Remove: List(lover is Female sex , 16 years old, able to do : 'Love,Chat,Combination')

代码讲解###

  • object Sex extends Enumeration , 定义了枚举 Sex: 枚举类型为 Sex.Value ;
  • class Person(var name:String, var age:Int, var ables:List[String], val sex:Sex.Value) 将类定义与主构造器结合起来。 使用 var ables:String 可以使得Scala自动生成 ables() 和 ables_$eq() 方法, 从而可以用 p.ables 来引用(实际上引用的是 ables() 方法); 如果不写 var 是不会自动生成相应方法的,也就不能用 p.ables 来引用了。
  • s"${this.name} is ${this.sex} sex , ${this.age} years old, able to do : " + this.ables.mkString("'",",", "'") 显示了Scala 中字符串插值的用法;

函数式编程####

核心都在对象 PersonsVisitor 里。

  • getField 使用 map 函数动态可配置地提取对象列表的指定字段的值列表;
  • filter 使用 filter 函数根据指定条件函数 accept 过滤出所需要的对象列表;
  • aggregate 则展示了一类常用操作:根据指定条件函数 accept 过滤出所需要的对象列表的某些值,然后对这些值做聚合操作,得到一个最终值;
  • ifSet 展示了一类常用操作: 根据指定条件函数 accept 过滤出所需要的对象并设置一些字段的值,得到改变后的对象列表;
  • ifRemove 展示了一类相对少见的操作: 根据指定条件函数 accept 直接从原列表中移除指定元素, 通常是有点对空间开销过于敏感了。注意到,这里使用了迭代器作为中间层,通过迭代器指向不满足条件的元素并返回其列表,不可变地实现获得“从原列表中移除指定元素后的原列表”。 实际上原列表并没有变化,只是通过迭代器实现了移除元素的视图。有点类似SQL 的 View 概念。

循环消失了么####

循环消失了么? No ! 是,也不是。 循环从业务代码中消失了。 但它并不是真正彻底底从代码里消失了。 循环被隐藏在抽象层里。 这样有什么益处呢? 抽象层的最重要作用就是“分离关注点”。 ORM 抽象层分离了“数据访问与对象之间的转化”的关注点, Storm框架抽象层分离了“分布式计算模型、拓扑以及节点消息传递”的关注点,使得应用只关注业务层的逻辑。

函数式编程也是一样,分离了“批量、流式处理列表数据的基础流程逻辑” 的关注点,使得业务层只需要专注于元素处理和获取结果。 你不必一次次写 foreach XXX , 而是只要编写定制的业务逻辑方法即可。

更通用的版本###

可以使用泛型将 PersonsVisitor 写得更通用一些。

  1. trait FieldValue {
  2. def getValue(fieldName:String):Any = {}
  3. }
  4. object Visitor {
  5. /**
  6. * Map Processing Pattern
  7. * Fetch part fields from object list.
  8. */
  9. def getField[T <: FieldValue](objs:List[T], fieldName:String):List[Any] = {
  10. objs.map(p => p.getValue(fieldName))
  11. }
  12. /**
  13. * Filter Processing Pattern
  14. * Get some objects satisfy condition function from object list
  15. */
  16. def filter[T](objs:List[T], accept:(T => Boolean)): List[T] = {
  17. objs.filter(accept)
  18. }
  19. /**
  20. * Aggregation Operation Pattern, eg. concat, sum
  21. */
  22. def aggregate[R,T](objs:List[R], op: (R => T), aggre: (List[T] => Any)): Any = {
  23. aggre(objs.map(op))
  24. }
  25. /**
  26. * If-Set Operation Pattern
  27. */
  28. def ifSet[T](objs:List[T], accept:(T=>Boolean), setFunc: (T=>Unit)): List[T] = {
  29. objs.foreach { p => if (accept(p)) { setFunc(p) } }
  30. objs
  31. }
  32. /**
  33. * If-Remove Operation Pattern
  34. */
  35. def ifRemove[T](objs:List[T], accept:(T=>Boolean)):Iterator[T] = {
  36. objs.iterator.filter(p => ! accept(p) )
  37. }
  38. def buildPersons():List[Person] = {
  39. List( new Person("Lier", 20, List("Study", "Explore","Combination"), Sex.Male),
  40. new Person("lover", 16, List("Love","Chat","Combination"), Sex.Female),
  41. new Person("Tender", 18, List("Care", "Combination"), Sex.Double))
  42. }
  43. }
  44. object NoRepeatForeachGeneral extends App {
  45. launch()
  46. def launch():Unit = {
  47. val persons = Visitor.buildPersons()
  48. println(Visitor.getField(persons, "name"))
  49. println(Visitor.getField(persons, "ables"))
  50. println(Visitor.getField(persons, "sex"))
  51. println(Visitor.getField(persons, "none"))
  52. Visitor.filter(persons, (p:Person) => p.ables.contains("Care")).foreach { println _ }
  53. println("All ables: " + Visitor.aggregate(persons, (p:Person)=>p.ables.mkString(","), (ablelist:List[String]) => ablelist.toSet.mkString(",")))
  54. println("Total age: " + Visitor.aggregate(persons, (p:Person)=>p.age, (agelist:List[Int]) => agelist.sum))
  55. println("If-Set:" + Visitor.ifSet(persons, (p:Person)=> p.age >= 18, (p:Person)=> p.setAge(p.age+1) ))
  56. println("If-Remove: " + Visitor.ifRemove(persons, (p:Person)=> p.age >= 18).toList)
  57. }
  58. }

代码讲解二###

  • 为了将 getField 泛型化, 需要保证类型 T 具有 getValue 方法,这通常通过定义接口来实现约束关系。 定义一个含有 getValue 方法的 trait FieldValue , 然后在泛型声明中声明 T <: FieldValue, 表明 T 是 FieldValue 的子类型,这样,Scala 可以推断出 T 类型可以调用 getValue 方法了。
  • 注意到,当 Visitor 通过泛型更加通用化后,客户端代码会有一些负担。 原来只要写成 p => p.age >= 18 , 现在需要写成 (p:Person) => p.age >= 18 。 必须声明参数类型,否则 Scala 无法判断 p 是否有方法 age()。

柯里化改造###

为了让客户端代码写得更舒服些,应该尽量让Scala自行推导出 p 的类型拥有 age() 方法。一开始是想用泛型, 定义 class Visitor[T] 或 trait Visitor[T]; 可是 Scala无法将函数里的函数参数 (比如 accept:(T=>Boolean)) 的类型 T 推导成带入的类型: val visitor = new Visitor[Person]. 在文章 Scala类型推导 谈到柯里化可以做到这一点,立即尝试了,是可行的。柯里化实际上就是将一次多参数调用过程分解成多个单参数调用步骤。见如下代码。能否用泛型来实现,作为一个待解之谜。 

  1. object Visitor {
  2. /**
  3. * Map Processing Pattern
  4. * Fetch part fields from object list.
  5. */
  6. def getField[T <: FieldValue](objs:List[T], fieldName:String):List[Any] = {
  7. objs.map(p => p.getValue(fieldName))
  8. }
  9. /**
  10. * Filter Processing Pattern
  11. * Get some objects satisfy condition function from object list
  12. */
  13. def filter[T](objs:List[T])(accept:(T => Boolean)): List[T] = {
  14. objs.filter(accept)
  15. }
  16. /**
  17. * Aggregation Operation Pattern, eg. concat, sum
  18. */
  19. def aggregate[R,T](objs:List[R])(op: (R => T))(aggre: (List[T] => Any)): Any = {
  20. aggre(objs.map(op))
  21. }
  22. /**
  23. * If-Set Operation Pattern
  24. */
  25. def ifSet[T](objs:List[T])(accept:(T=>Boolean))(setFunc: (T=>Unit)): List[T] = {
  26. objs.foreach { p => if (accept(p)) { setFunc(p) } }
  27. objs
  28. }
  29. /**
  30. * If-Remove Operation Pattern
  31. */
  32. def ifRemove[T](objs:List[T])(accept:(T=>Boolean)):Iterator[T] = {
  33. objs.iterator.filter(p => ! accept(p) )
  34. }
  35. def buildPersons():List[Person] = {
  36. List( new Person("Lier", 20, List("Study", "Explore","Combination"), Sex.Male),
  37. new Person("lover", 16, List("Love","Chat","Combination"), Sex.Female),
  38. new Person("Tender", 18, List("Care", "Combination"), Sex.Double))
  39. }
  40. }
  41. object NoRepeatForeachSoft extends App {
  42. launch()
  43. def launch():Unit = {
  44. val persons = Visitor.buildPersons()
  45. println(Visitor.getField(persons, "name"))
  46. println(Visitor.getField(persons, "ables"))
  47. println(Visitor.getField(persons, "sex"))
  48. println(Visitor.getField(persons, "none"))
  49. Visitor.filter(persons)(p => p.ables.contains("Care")).foreach { println _ }
  50. println("All ables: " + Visitor.aggregate(persons)(p=>p.ables.mkString(","))(ablelist => ablelist.toSet.mkString(",")))
  51. println("Total age: " + Visitor.aggregate(persons)(p=>p.age)(agelist => agelist.sum))
  52. println("If-Set:" + Visitor.ifSet(persons)(p=> p.age >= 18)(p=> p.setAge(p.age+1)))
  53. println("If-Remove: " + Visitor.ifRemove(persons)(p=> p.age >= 18).toList)
  54. }
  55. }

代码讲解三###

举一个简单的函数来说。filter初始定义是两个参数: def filter[T](objs:List[T], accept:(T => Boolean)): List[T],传入一个T类型的对象列表和一个以T类型对象为参数的条件函数。柯里化之后:def filter[T](objs:List[T])(accept:(T => Boolean)): List[T],参数未变,编写形式发生了变化,调用方式也发生了变化: Visitor.filter(persons)(p => p.ables.contains("Care")) . 类似于一个二元函数求值,可以一次性将参数全部代入,也可以一次代入一个参数求值。 

注意到,客户端代码中传入的函数再也不需要指明参数类型了。Scala可以根据调用者对象自动推导出传入函数的参数类型。

小结###

可以看到,使用函数式编程,将通用流程处理(遍历-条件-执行操作)与定制业务逻辑(业务对象列表、业务操作)清晰地分离开,各司其责。业务代码再也不用充斥一条条单调无味的foreach语句了。

有人说,函数式编程有内存和性能开销,高阶函数的可理解性和可维护性相对较低,应用于大型工程可能有潜在风险。对此,我的观点是:语言和技术终会进化,今日所忧虑的问题在明日会变成家常便饭一样接受。勇往直前吧。

使用函数式编程消除重复无聊的foreach代码(Scala示例)的更多相关文章

  1. 编程中的链式调用:Scala示例

    编程中的链式调用与Linux Shell 中的管道类似.Linux Shell 中的管道 ,会将管道连接的上一个程序的结果, 传递给管道连接的下一个程序作为参数进行处理,依次串联起N个实用程序形成流水 ...

  2. Scala入门系列(十):函数式编程之集合操作

    1. Scala的集合体系结构 Scala中的集合体系主要包括(结构跟Java相似): Iterable(所有集合trait的根trait) Seq(Range.ArrayBuffer.List等) ...

  3. Scala学习教程笔记三之函数式编程、集合操作、模式匹配、类型参数、隐式转换、Actor、

    1:Scala和Java的对比: 1.1:Scala中的函数是Java中完全没有的概念.因为Java是完全面向对象的编程语言,没有任何面向过程编程语言的特性,因此Java中的一等公民是类和对象,而且只 ...

  4. Java8函数式编程探秘

    引子 将行为作为数据传递 怎样在一行代码里同时计算一个列表的和.最大值.最小值.平均值.元素个数.奇偶分组.指数.排序呢? 答案是思维反转!将行为作为数据传递. 文艺青年的代码如下所示: public ...

  5. 9、scala函数式编程-集合操作

    一.集合操作1 1.Scala的集合体系结构 // Scala中的集合体系主要包括:Iterable.Seq.Set.Map.其中Iterable是所有集合trait的根trai.这个结构与Java的 ...

  6. 如何编写高质量的 JS 函数(4) --函数式编程[实战篇]

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/ZoXYbjuezOWgNyJKmSQmTw作者:杨昆 [编写高质量函数系列],往期精彩内容: ...

  7. scala 函数式编程之集合操作

    Scala的集合体系结构 // Scala中的集合体系主要包括:Iterable.Seq.Set.Map.其中Iterable是所有集合trait的根trai.这个结构与Java的集合体系非常相似. ...

  8. Scala:函数式编程之下划线underscore

    http://blog.csdn.net/pipisorry/article/details/52913548 python参考[python函数式编程:apply, map, lambda和偏函数] ...

  9. Python3基础(3)集合、文件操作、字符转编码、函数、全局/局部变量、递归、函数式编程、高阶函数

    ---------------个人学习笔记--------------- ----------------本文作者吴疆-------------- ------点击此处链接至博客园原文------ 1 ...

随机推荐

  1. python 版Faster Rcnn

    直接按照官网https://github.com/rbgirshick/py-faster-rcnn上的教程对faster Rcnn进行编译的时候,会发有一些层由于cudnn版本的更新,会报错如下: ...

  2. oc培训之变量课后练习

    1.打印常用数据类型长度,打印2.3f,使小数点后面为4位. float i=2.3f; printf("%.4f",i); 2.打印以下图形. int i,j,k,m,n; ;i ...

  3. 细细探究MySQL Group Replicaiton — 配置维护故障处理全集(转)

    如果转载,请注明博文来源: www.cnblogs.com/xinysu/   ,版权归 博客园 苏家小萝卜 所有.望各位支持! 

  4. npm install webpack -g

    npm install webpack -g   全局安装webpack

  5. LSTM输入层、隐含层及输出层参数理解【转载】

    转自:https://blog.csdn.net/yyb19951015/article/details/79740869 //这个博客讲的挺不错的. http://www.newlifeclan.c ...

  6. 【Redis】事务

    在Redis中,事务是以multi/exec/discard进行的, 其中multi表示事务的开始, exec表示事务的执行,discard表示丢弃事务. > multi # 事务的开始 OK ...

  7. CLR总览

    Contents 第1章CLR的执行模型... 4 1.1将源代码编译成托管代码模块... 4 1.2 将托管模块合并成程序集... 6 1.3加载公共语言运行时... 7 1.4执行程序集的代码.. ...

  8. 如何在Android的ListView中构建CheckBox和RadioButton列表(支持单选和多选的投票项目示例)

    引言 我们在android的APP开发中有时候会碰到提供一个选项列表供用户选择的需求,如在投票类型的项目中,我们提供一些主题给用户选择,每个主题有若干选项,用户对这些主题的选项进行选择,然后提交. 本 ...

  9. 安装Cuda9.0+cudnn7.3.1+tensorflow-gpu1.13.1

    我的安装版本: win10 x64 VS2015 conda python 3.7 显卡 GTX 940mx Cuda 9.0 cudnn v7.3.1 Tensorflow-gpu 1.13.1 1 ...

  10. cocos2dx 游戏plist与png完美切成小图python代码

    首先需要一份python的切图程序: #python2.5 unpack_plist.py birdfly #! /usr/lical/bin/python import os,sys from xm ...