函数式编程 (FP) 是关于避免重新分配变量、避免可变数据结构、避免状态并始终支持函数。如果我们将函数式技术应用于日常 Java 代码,我们可以从 FP 中学到什么?
在这个名为“Functional Java by Example”的系列中,我将分 8 期重构一段现有代码,看看我是否可以在 Java 中实现 Functional Nirvana。
我在“真正的”函数式语言(如 Haskell 或 F#)方面没有太多经验,但我希望在每篇文章中通过示例来演示将其中一些实践应用于日常 Java 代码意味着什么。
希望最后你已经获得了一些洞察力并知道选择一些有利于你自己的代码库的技术。
这些是所有部分:
我将在每篇文章发表时更新链接。如果您通过内容联合阅读本文,请查看我的博客 上的原始文章。
每次也将代码推送到此 GitHub 项目。
免责声明:代码是用 Apache Groovy 编写的,主要是为了简洁,所以我不必在不需要的地方输入内容(你知道:输入)重要的例子。其次,这种语言让我开心。
如果您不是在Groovy实时流数据事件处理框架上使用 Haskell、F# 或 Scala,您不妨收拾行李。这些天甚至 JavaScript 人员都在围绕您的方法旋转函数——而且这种语言已经存在了一段时间。
有很多文章和视频让您相信,如果您现在不跟上函数式的潮流,您就会被旧的 OOP 装置抛在后面,坦率地说,几年内就过时了。
好吧,我在这里告诉你这不完全正确,但是 FP 确实有一些前提,例如可读性、可测试性和可维护性,我们也在我们的(企业)Java 代码中努力实现的价值观对吗?
当您阅读本文时,多年来您可能已经直言不讳地认为 FP 是向前或向后或 anno 2017-2018 的一步,您只是乐于接受新想法
您可以通过学习 FP 来提高您的每种语言的技能。
自己确定您可以从中学到什么,以及您自己的编程如何从中受益。
如果你能胜任这项任务,让我们从这个系列开始……
关于示例代码的一句话:为像这样的博客想出人为的示例是相当棘手的:它应该足够容易以吸引广大受众,足够简单以便无需太多内容就可以理解情境,但仍然足够有趣以产生预期的学习效果。
展望未来,本系列的每一期都将建立在前一期的基础上。下面是我们将作为起点的代码。
所以,戴上你的眼镜,看看你是否熟悉下面的编码风格。
class FeedHandler { Webservice webservice DocumentDb documentDb void handle(List<Doc> changes) { for (int i = 0; i < changes.size(); i++) { def doc = changes[i] if (doc.type == 'important') { try { def resource = webservice.create(doc) doc.apiId = resource.id doc.status = 'processed' } catch (e) { doc.status = 'failed' doc.error = e.message } documentDb.update(doc) } } } }
FeedHandler
。Webservice
类,一个是DocumentDb
类。handle
方法可以处理 Doc
对象列表。文件?试着弄清楚这里发生了什么
..
..
..
完毕?
读这样的东西有时会让你觉得自己像个人类解析器。
扫描类名 (FeedHandler?
) 和一个方法 (void handle
) 可以让您在有些眼睛酸痛的情况下,对一切的目的有一种“感觉”。
但是,弄清楚 handle
方法中到底“处理”了什么要困难得多。
for-loop
— 但究竟迭代了什么?多少次?webservice
被调用,返回一个叫做 resource
的东西。webservice
成功返回,被迭代的 doc
(文档?)会更新状态。webservice
也可以抛出一个 Exception
,它被捕获并且文档被更新为另一个状态。documentDb
实例“更新”。看起来像一个数据库。doc.type
。或许,你听说过这样一句话:
阅读代码的次数多于编写代码的次数。
看看这片美丽:
for (int i = 0; i < changes.size(); i++) {
上面的代码是以命令式 风格编写的,这意味着具体的语句——操纵状态和行为——是明确写出来的。
int i
int i
小于 changes
列表的大小时循环int i
加 1在这种命令式(过程式)编码风格中(大多数主流语言,包括面向对象编程 (OOP) 语言,如 Java、C++、C# 的设计主要支持这种风格),开发人员编写计算机完成特定任务所需执行的确切语句。
一些非常命令式(过程性)代码的信号:
代码明确关注“如何”——这使得“什么”难以确定。
我们的第一步,正如本文的标题已经提到的那样,是摆脱命令式编码风格并重构为更具声明性风格——FP是表单。
循环 最让我烦恼。
这是新版本的代码。
class FeedHandler { Webservice webservice DocumentDb documentDb void handle(List<Doc> changes) { // for (int i = 0; i < changes.size(); i++) { // def doc = changes[i] changes .findAll { doc -> doc.type == 'important' } .each { doc -> try { def resource = webservice.create(doc) doc.apiId = resource.id doc.status = 'processed' } catch (e) { doc.status = 'failed' doc.error = e.message } documentDb.update(doc) } } }
有什么变化?
if (doc.type == 'important')
部分在文档集合本身上再次被替换为 findAll { doc -> doc.type == 'important' }
— 意思是“找到所有重要的文档并返回一个新集合只有那些重要文件”for-loop
(带有中间 i
变量)已被文档集合本身的声明式 each
方法所取代 — 意思是“为列表中的每个文档执行一段代码,我不在乎你是怎么做的” ��不用担心 each
和 findAll
:这些方法是由 Groovy 添加的,我很高兴在同一代码库中将它们与 Java 一起用于任何集合,例如设置、列表、Map。 Vanilla Java 8 具有等效的机制,例如 forEach
以更具声明性的方式迭代集合。
导致可读软件的是:
描述“什么”,而不是“如何”。
如果我以更功能性的风格编写代码,我可以很容易地看到发生了什么,这节省了我的时间(因为是的,我确实阅读 90% 的时间是编写代码而不是编写代码)并且像这样编写代码不易出错,因为行数越少,错误隐藏的机会就越少。
这是现在
在第 2 部分中,我们将正确命名,为更多函数式编程铺平道路,例如本系列后面的“Either”或“Try”。
标签2: Java教程地址:https://www.cundage.com/article/jcg-functional-java-example-part-1-imperative-declarative.html