Scala编程(第5版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第11步 识别函数式编程风格

正如第1章提到的,Scala允许采用指令式编程风格,但鼓励采用函数式编程风格。如果你之前的编程背景是指令式的(如果你是一个Java程序员),那么当你学习Scala时的一个主要的挑战是弄清楚如何使用函数式编程风格。我们意识到这个风格对你来说可能一开始并不熟悉,本书将致力于引导你做出这个转变。这也需要你自己的努力,我们鼓励你这样做。如果你之前更多的是采用指令式编程风格,那么我们相信学习函数式编程风格将帮助你拓宽视野,成为更好的Scala程序员。

首先从代码层面识别两种风格的差异。一个显著的标志是如果代码包含任何var变量,则它通常是指令式风格的;而如果代码完全没有var(也就是说代码只包含val),则它很可能是函数式的。因此,一个向函数式编程风格转变的方向是尽可能不用var

如果你之前用的是指令式的编程语言,如Java、C++或C#,则可能认为var是常规的变量而val是特例;而如果你之前更多地使用函数式的编程语言,如HaskellOCamlErlang,则可能会认为val是常规的变量而var简直是对编程的亵渎。在Scala看来,valvar不过是工具箱中的两种不同的工具,都有相应的用途,没有哪一个本质上是不好的。Scala更偏向于鼓励你使用val,但你最终要根据自己手里的工作选择最适用的工具。然而就算你认同这个平衡的观点,仍然可能在一开始难以想明白如何从你的代码中去掉var

参考如下这个while循环的例子(改编自第2章),由于使用了var,因此它是指令式编程风格的:

可以将这段代码转换成更函数式的编程风格,去掉var,就像这样:

或者这样:

这个例子展示了编程中使用更少的var的好处。经过重构的(更函数式的)代码与原始的(更指令式的)代码相比,更清晰、更精简,也更少出错。Scala鼓励使用函数式编程风格的原因就是这样能帮助你实现更易读、更少出现错误的代码。

不过你可以走得更远。重构后的printArgs方法并不是“纯”的函数式代码,因为它有副作用(本例中它的副作用是向标准输出流打印)。带有副作用的函数的标志性特征是结果类型为Unit。如果一个函数并不返回任何有意义的值,也就是Unit这样的结果类型所表达的意思,那么这个函数存在的唯一意义就是产生某种副作用。一个更函数式的做法是定义一个将传入的args进行格式化(用于打印)的方法,但只是返回这个格式化的字符串,如示例3.9所示。

示例3.9 一个没有副作用或var的函数

现在你真的做到了函数式:没有副作用,也没有varmkString方法可以被用于任何可被迭代访问的集合(包括数组、列表、集和映射),返回一个包含了对所有元素调用toString方法的结果的字符串,并以传入的字符串分隔。因此,如果args包含3个元素,即"zero""one""two",则formatArgs将返回"zero\none\ntwo"。当然,这个函数实际上并不像printArgs那样打印出任何东西,但是可以很容易地将它的结果传递给println来达到这个目的:

每个有用的程序都会有某种形式的副作用;否则,它对于外部世界就没有任何价值。倾向于使用无副作用的函数可以促使你设计出将带有副作用的代码最小化的程序。这样做的好处之一是让你的程序更容易测试。

例如,要测试本节给出的3个printArgs方法,需要重新定义println,捕获传递给println的输出,确保它是你预期的样子。而要测试formatArgs则很简单,只需要检查它的结果即可:

Scala的assert方法用于检查传入的Boolean,如果传入的Booleanfalse,则抛出AssertionError;如果传入的Booleantrue,则安静地返回assert。你将在第25章了解到更多关于断言assertion)和测试的内容。

尽管如此,请记住var或副作用从本质上讲并非不好。Scala并不是一门纯函数式编程语言,强制你只能用函数式风格来编程。Scala是指令式/函数式混合hybrid)编程语言。你会发现在有些场景下对要解决的问题而言指令式更为适合,这个时候不要犹豫,使用指令式的编程风格就好。为了让你学习如何不使用var完成编程任务,我们将在第7章向你展示许多具体的用到var的代码示例,并告诉你如何将这些var转换成val

Scala程序员的平衡心态

倾向于使用val、不可变对象和没有副作用的方法,优先选择这些方法。不过当你有特定的需要和理由时,也不要拒绝var、可变对象和带有副作用的方法。