关于本系列

本系列旨在将您的观点重新定位为实用的心态,帮助您以新的方式看待常见问题并找到改善日常编码的方式。 它探讨了函数式编程的概念,允许使用Java™语言进行函数式编程的框架,在JVM上运行的函数式编程语言以及语言设计的一些未来方向。 该系列面向那些了解Java及其抽象如何工作,但很少或没有使用功能语言经验的开发人员。

在本系列的第一部分中,我开始讨论函数式编程的一些特征,展示了这些思想在Java语言和更多函数式语言中如何体现。 在本文中,我将通过讨论一流的函数,优化和闭包来继续概念之旅。 但是本部分的基本主题是控制 :何时需要,何时需要以及何时放手。

一流的功能和控制

使用功能的Java库(参见相关主题 ),我最后表现出与功能性的一些分类的实施isFactor()factorsOf()方法,如清单1所示:

清单1.数字分类器的功能版本
import fj.F;
import fj.data.List;
import static fj.data.List.range;
import static fj.function.Integers.add;
import static java.lang.Math.round;
import static java.lang.Math.sqrt;public class FNumberClassifier {public boolean isFactor(int number, int potential_factor) {return number % potential_factor == 0;}public List<Integer> factorsOf(final int number) {return range(1, number+1).filter(new F<Integer, Boolean>() {public Boolean f(final Integer i) {return number % i == 0;}});}public int sum(List<Integer> factors) {return factors.foldLeft(fj.function.Integers.add, 0);}public boolean isPerfect(int number) {return sum(factorsOf(number)) - number == number;}public boolean isAbundant(int number) {return sum(factorsOf(number)) - number > number;}public boolean isDeficiend(int number) {return sum(factorsOf(number)) - number < number;}
}

isFactor()factorsOf()方法中,我将对循环算法的控制权交给了框架-现在,它决定如何在数字范围内进行最佳迭代。 如果该框架(或者-如果您选择Clojure或Scala之类的功能语言-该语言)可以优化基础实现,那么您将自动受益。 尽管您起初可能不愿意放弃这么多的控​​制,但是请注意,它遵循编程语言和运行时的普遍趋势:随着时间的流逝,开发人员对细节的抽象度越来越高,平台可以更有效地处理这些细节。 我从来不用担心JVM上的内存管理,因为该平台允许我忘记它。 当然,它有时会使事情变得更加困难,但这是对您在日常编码中获得的好处的一个很好的权衡。 功能性的语言结构,如高阶和一流的功能让我爬上一个更响了抽象阶梯,更专注于代码的功能,而不是它是怎么做的。

即使使用Functional Java框架,使用Java进行这种样式的编码也很麻烦,因为该语言实际上没有语法和构造方法。 用某种语言编写的函数编码看起来像什么?

Clojure中的分类器

Clojure是为JVM设计的功能性Lisp(请参阅参考资料 )。 考虑用Clojure编写的数字分类器,如清单2所示:

清单2.数字分类器的Clojure实现
(ns nealford.perfectnumbers)
(use '[clojure.contrib.import-static :only (import-static)])
(import-static java.lang.Math sqrt)(defn is-factor? [factor number](= 0 (rem number factor)))(defn factors [number] (set (for [n (range 1 (inc number)) :when (is-factor? n number)] n)))(defn sum-factors [number] (reduce + (factors number)))(defn perfect? [number](= number (- (sum-factors number) number)))(defn abundant? [number](< number (- (sum-factors number) number)))(defn deficient? [number](> number (- (sum-factors number) number)))

即使您不是一个顽固的Lisp开发人员, 清单2中的大多数代码也很容易遵循,特别是如果您可以学习由内而外的阅读。 例如, is-factor? 方法采用两个参数,并询问将number乘以factor时,余number是否等于0。 同样, perfect? abundant?deficient? 方法应该易于理解,特别是如果您参考清单1中的Java实现。

sum-factors方法使用内置的reduce方法。 sum-factors使用作为每个元素上的第一个参数提供的函数(在本例中为+ )一次减少列表中的一个元素。 reduce方法以几种语言和框架以不同的形式出现; 您在清单1的Functional Java版本中将其foldLeft()方法。 factors方法返回一个数字列表,因此我一次处理一个列表,将每个元素加到累加和中,这是reduce的返回值。 您会看到,一旦习惯了对高阶和一流函数的思考 ,就可以减少(双关语意)代码中的大量杂音。

factors方法似乎是符号的随机集合。 但是,一旦您了解了列表理解功能(Clojure中几个强大的列表操作功能之一),它的确有意义。 和以前一样,从内到外理解factors是最容易的。 不要被冲突的语言术语所迷惑。 Clojure中的for关键字并不表示for循环。 而是将其视为所有过滤和转换构造的祖父。 在这种情况下,我要求它使用is-factor?过滤从1到( number + 1)的数字范围is-factor? 谓词(这是我在清单2前面定义的is-factor方法-注意大量使用一流的函数),返回匹配的数字。 此操作的返回结果是符合筛选条件的数字列表,我将其强制转换为集合以除去重复项。

尽管学习新语言很麻烦,但是当您了解功能语言时,就会从功能语言中受益匪浅。

优化

切换到功能样式的好处之一是能够利用语言或框架提供的高阶功能支持。 但是,当您不想放弃控制权时,情况又如何呢? 在我之前的示例中,我将迭代机制的内部行为比作内存管理器的内部工作:大多数时候,您很乐意不担心那些细节。 但是有时候,您确实会关心它们,例如优化和类似的调整。

在“ 功能性思考,第1部分 ”中显示的数字分类器的两个Java版本中,我优化了确定因素的代码。 最初的幼稚实现使用效率极低的模数( % )运算符来检查从2到目标数字本身的每个数字,以确定它是否是一个因数。 您可以通过注意因素成对来优化算法。 例如,如果您正在寻找28的因数,那么当您找到2时,您也可以抓住14。 如果可以成对收集因子,则只需检查不超过目标数平方根的因子。

在Java版本中易于执行的优化在Functional Java版本中似乎是不可能的,因为我无法直接控制迭代机制的实现。 但是,学习功能性思考的一部分需要放弃关于这种控制的概念,从而使您能够发挥另一种控制作用。

我可以从功能上isFactor()原始问题:过滤从1到number所有因子,仅保留与我的isFactor()谓词匹配的因子。 这在清单3中实现:

清单3. isFactor()方法
public List<Integer> factorsOf(final int number) {return range(1, number+1).filter(new F<Integer, Boolean>() {public Boolean f(final Integer i) {return number % i == 0;}});
}

尽管从声明的角度来看很优雅,但是清单3中的代码效率很低,因为它检查每个数字。 一旦我了解了优化(成对收集因子,直到平方根),我就可以像下面这样重述问题:

  1. 过滤所有目标数字的因数,从1到数字的平方根。
  2. 将目标数量除以这些因子中的每一个,即可得到对称因子,并将其添加到因子列表中。

考虑到这一目标,我可以使用Functional Java库编写factorsOf()方法的优化版本,如清单4所示:

清单4.优化因素查找方法
public List<Integer> factorsOfOptimzied(final int number) {List<Integer> factors = range(1, (int) round(sqrt(number)+1)).filter(new F<Integer, Boolean>() {public Boolean f(final Integer i) {return number % i == 0;}});return factors.append(factors.map(new F<Integer, Integer>() {public Integer f(final Integer i) {return number / i;}})).nub();
}

清单4中的代码基于我先前所述的算法,以及Functional Java框架所需的一些时髦语法。 首先,我将数字范围从1到目标数字的平方根加1(以确保我能抓住所有因素)。 第二,我像包装在Functional Java代码块中一样,使用模数运算符(如先前版本)过滤结果。 我将此过滤后的列表保存在factors变量中。 第四(从内到外读取),我获取了这个因子列表并执行map()函数,该函数通过对每个元素执行我的代码块( 将每个元素映射到新值)来生成一个新列表。 我的因子列表包含目标数直至其平方根的所有因子; 我需要将每个目标数除以目标数以获得对称系数,这就是发送到map()方法的代码块所执行的操作。 第五,现在我有了对称因子的列表,我将其添加到原始列表中。 作为最后一步,我必须说明我将因子保留在List而不是Set的事实。 List方法对于这些类型的操作很方便,但是当弹出整数平方根时,我的算法的副作用是重复条目。 例如,如果目标数为16,则整数根4会两次出现在因子列表中。 为了继续使用便捷的List方法,我只需要在最后调用其nub()方法即可,该方法将删除所有重复项。

仅仅因为您通常在使用函数式编程等更高级别的抽象时就放弃了实现细节的知识,并不意味着您在必须做到的情况下不会陷入困境。 Java平台通常会使您免受低级内容的侵害,但是如果您下定决心,则可以深入到所需的级别。 同样,在函数式编程构造中,您通常愿意将细节放给抽象,从而保留真正重要的时间。

到目前为止,我所展示的所有Functional Java代码中视觉上的突出表现都是块语法,它使用泛型和匿名内部类作为伪代码块,闭包类型构造。 闭包是功能语言的常见功能之一。 是什么使它们在这个世界上如此有用?

闭包有什么特别之处?

闭包是一种对其中引用的所有变量进行隐式绑定的函数。 换句话说,函数(或方法)在其引用的内容周围包含一个上下文。 闭包在功能语言和框架中经常用作可移植的执行机制,并传递给诸如map()类的高阶函数作为转换代码。 函数式Java使用匿名内部类来模仿某些“真实的”关闭行为,但由于Java不支持闭包,因此它们不能一路走来。 但是,这是什么意思?

清单5显示了使闭包如此特别的示例。 它是用Groovy编写的,它通过其代码块机制支持闭包。

清单5. Groovy代码说明了闭包
def makeCounter() {def very_local_variable = 0return { return very_local_variable += 1 }
}c1 = makeCounter()
c1()
c1()
c1()
c2 = makeCounter()println "C1 = ${c1()}, C2 = ${c2()}"
// output: C1 = 4, C2 = 1

makeCounter()方法首先定义一个具有适当名称的局部变量,然后返回使用该变量的代码块。 请注意, makeCounter()方法的返回类型是代码块,而不是值。 该代码块不执行任何操作,只不过增加局部变量的值并返回它。 我在这段代码中放置了显式的return调用,这两个调用在Groovy中都是可选的,但是如果没有它们,代码将变得更加神秘!

为了练习makeCounter()方法,我将代码块分配给C1变量,然后调用它三次。 我正在使用Groovy的语法糖来执行代码块,该代码块是在代码块的变量旁边放置一组括号。 接下来,我再次调用makeCounter() ,将代码块的新实例分配给C2 。 最后,我再次执行C1C2 。 请注意,每个代码块都跟踪了very_local_variable的单独实例。 这就是封闭上下文的意思。 即使在方法中定义了局部变量,代码块仍会绑定到该变量,因为它引用了该变量,这意味着在代码块实例处于活动状态时,它必须跟踪该变量。

清单6中出现了与Java中可能发生的最接近的相同行为:

清单6. Java中的MakeCounter
public class Counter {private int varField;public Counter(int var) {varField = var;}public static Counter makeCounter() {return new Counter(0);}public int execute() {return ++varField;}
}

Counter类可以有几种变体,但是您仍然要自己管理状态。 这说明了为什么使用闭包可以举例说明功能思想:允许运行时管理状态。 与其强迫您处理字段创建和婴儿状态(包括在多线程环境中使用代码的可怕前景),不如让语言或框架为您无形地管理该状态。

我们最终将在即将发布的Java版本中获得闭包(所幸的是,其讨论超出了本文的范围)。 它们在Java中的出现将带来两个受欢迎的好处。 首先,它将大大简化框架和库编写器的功能,同时改善其语法。 其次,它将为在JVM上运行的所有语言的闭包支持提供一个低级的通用标准。 即使许多JVM语言都支持闭包,它们都必须实现自己的版本,这使得在语言之间传递闭包变得很麻烦。 如果Java语言定义了一种格式,则所有其他语言都可以利用它。

结论

放弃对低级细节的控制是软件开发的普遍趋势。 我们很高兴地放弃了垃圾回收,内存管理和硬件差异的责任。 函数式编程代表了下一个抽象飞跃:将更多平凡的细节(例如迭代,并发和状态)分配给运行时。 这并不意味着您无法在必要时收回控制权,但是您必须要拥有控制权,而不是将其强加于您。

在下一部分中,我将通过介绍currying和Partial Method Application继续探索Java和近亲中的函数式编程构造。


翻译自: https://www.ibm.com/developerworks/java/library/j-ft2/index.html

整体功能大于部分功能之和_功能性思考,第2部分相关推荐

  1. 整体功能大于部分功能之和_功能性思考,第3部分

    关于本系列 本系列旨在将您的观点重新定位为实用的心态,帮助您以新的方式看待常见问题并找到改善日常编码的方式. 它探讨了函数式编程的概念,允许在Java语言中进行函数式编程的框架,在JVM上运行的函数式 ...

  2. 整体功能大于部分功能之和_功能性思考,第1部分

    关于本系列 本系列旨在将您的观点重新定位为实用的心态,帮助您以新的方式看待常见问题并找到改善日常编码的方式. 它探讨了函数式编程的概念,允许在Java语言中进行函数式编程的框架,在JVM上运行的函数式 ...

  3. azure多功能成像好用吗_了解Azure持久功能

    azure多功能成像好用吗 Stateful Workflows on top of Stateless Serverless Cloud Functions-this is the essence ...

  4. 功能安全 李艳文_如何理解功能安全管理

    功能安全标准ISO 26262在Part2部分讲述了功能安全管理的内容.但是大家在进行具体功能安全项目开发过程,往往将功能安全管理归结为流程,在实际开发过程不重视,甚至功能安全管理往往被忽视掉,而是直 ...

  5. azure多功能成像好用吗_如何使用Azure功能处理高吞吐量消息

    azure多功能成像好用吗 Authored with Steef-Jan Wiggers, Azure MVP. 由Azure MVP Steef-Jan Wiggers撰写. With Micro ...

  6. 华为隐藏功能扩大内存代码大全_发现将华为手机这3个功能打开,竟然可以将手机性能极限发挥...

    手机用久了,性能难免会跟不上,明明安装软件不多,内存也充足,但手机运行卡顿,而且使用时间越长手机就越卡.尤其是安卓系统手机,用上一年后,手机就越来越卡,日常使用玩游戏都很不给力,反之苹果手机因IOS系 ...

  7. 功能安全 李艳文_李艳文:智能网联全新安全问题凸显 相关自动驾驶事故逐年增加...

    [摘要]随着智能网联汽车先进功能越来越多,集成度.复杂性增加,新安全问题开始凸显,由功能安全和预期功能安全引发的自动驾驶事故比例逐渐增多. 8月13日-15日,以"新变局 新挑战 新思路-- ...

  8. macf键与功能键切换_功能切换(功能开关或功能标志)与功能分支

    macf键与功能键切换 功能分支 如果使用分支,则表示不进行持续集成/部署/交付 ! 您可能具有很好的单元测试 代码覆盖率 ,可能正在执行TDD ,可能已经以BDD格式编写了功能和集成测试,并且可能在 ...

  9. 功能安全 李艳文_李艳文:智能网联全新安全问题凸显相关自动驾驶事故逐年增加...

    [摘要]随着智能网联汽车先进功能越来越多,集成度.复杂性增加,新安全问题开始凸显,由功能安全和预期功能安全引发的自动驾驶事故比例逐渐增多. 8月13日-15日,以"新变局新挑战新思路--引领 ...

最新文章

  1. android.view.InflateException: Binary XML file line #16: Binary XML file line #16: Error inflating
  2. pythonshell画图_Python Shell下使用matplotlib
  3. 黄金分割算法求函数的极值C++实现
  4. Class的 getSuperclass与getGenericSuperclass区别
  5. 【转】ABP源码分析三十六:ABP.Web.Api
  6. LeetCode 1457. 二叉树中的伪回文路径(位运算+递归)
  7. MYSQL数据库查询删除创建企业基本知识
  8. mac自动生成路径问题
  9. Linux磁盘分区详解(parted)
  10. C++11强类型枚举——枚举类
  11. 在WPF里面实现以鼠标位置为中心缩放移动图片
  12. 跨界打劫!中医保健店用一招免费洗车,快速引流进店,月赚20万
  13. 含义:Web1.0、Web2.0、Web3.0、Web4.0、Web5.0、Web6.0
  14. 数学三角形和倒三角形公式表达了什么
  15. Pandas熊猫框架
  16. pythoncad标注教程_CAD 2014二维三维建模渲染标注基础与提升视频教程
  17. Git ---- 自建代码托管平台-GitLab
  18. 获取google Map API Key方法
  19. vscode的调试配置
  20. 修改testlink服务器ip,简单明了的TestLink配置手册

热门文章

  1. 查找树的LCA(Least Common Ancestor)算法
  2. 放大/ 缩小浏览器页面(或在线网页)
  3. ​ACL 2023 | 用二分类解决无监督常识问答
  4. c# vs编程中x,y的坐标单位是什么
  5. 阿里巴巴马云:CEO的本事就是会用别人脑袋
  6. 【硬件设计】降压电源电路设计
  7. kali、debian、Ubuntu中安装微信、qq、百度网盘
  8. 360软件管家怎么下载python_​再见,360安全卫士
  9. 蛋花花分享程序员赚外快有哪些方法
  10. [足式机器人]Part1 双足和四足的运动Ch04——【Legged Robots that Balance 读书笔记】