Scala如何改变了我的编程风格:从命令式到函数式

51CTO编辑推荐: Scala编程语言专题

【51CTO快译】编者前言:这篇文章最初写于2008年底,作者Bill Venners一方面是美国著名开发网站Artima的总编,另一方面也是一位十分关注Scala语言的Java程序员。在这几个月间的Scala创始人Martin Odersky访谈系列中,与Martin对话的正是Bill Venners。这篇文章虽然已经完成了半年有余,但对于还不很熟悉Scala语言的Java程序员而言,仍然是一篇非常实用的Scala语言简介。以下是译文:

每次我学习一门新的语言,我都会学到某些编程方面的东西。比如说,当我以一个C++程序员的身份学习Java的时候,Java的接口构造教会我来自纯粹的抽象基类的多重继承的价值。尽管在C++里面这种编程风格是有可能的,但在我使用C++的日子里,我却没有考虑用这种方式进行多重继承,而我在C++设计中也不怎么使用抽象基类。然而,一旦我开始进行Java编程,我就开始一直使用这种风格了。学习Java—尤其是它的接口构造—改变了我OO设计的方法。

51CTO编辑推荐: 充分利用面向对象语言的接口特性 | 面向对象的思维过程

我学习 Scala编程的时候也发生了类似的情况。在过去的两年里,我有相当多的时间是用 Scala工作的,Scala是Java平台上的一种新的静态类型语言,它融合了面向对象编程和函数型程序设计的概念。Scala能让我写出几乎跟Ruby和Python 一样简洁的代码。在 Scala 我可以跟在 Java 里面一样方便地调用 Java 库,包括我已有的 Java 库。考虑到 Scala 是静态类型的,我可以享受到诸多静态类型的好处,诸如将文档作为类型, IDE 代码自动完成,动态代码重构( deterministic refactoring )以及执行速度等( Scala 程序的执行速度跟 Java 的一样快)。但 Scala 还让我以简洁和类型安全的方式获得某些通常是动态语言的好处,例如在已有类上增加新方法的能力,或者将类型传递给没有共同继承关系的方法。

Scala 是怎样改变了我对编程的看法的呢?一句话:我学会了欣赏函数化的风格。函数化的编程风格强调不可变对象、变量可被初始化但不能重新赋值( Java 中的最终变量)、数据结构转换,以及方法和控制的构造,最终产生一个没有副作用的结果。这个领域的另一端是命令式的风格,以可变对象、变量可被重新赋值( Java 里的正常变量)、在数据结构中索引、以及带副作用的方法和控制构造为特征。

尽管 Scala 经常被吹捧为函数型编程语言,当它并不仅仅是函数型的。 Scala 同时支持函数式和命令式两种风格。如果你自己选择要这么做的话,你可以以 Java 的编程方式进行 Scala 编程,那种风格主要是命令式的。这样有助于 Scala 的学习曲线变缓,但随着对 Scala 越来越熟悉,你就会发现自己会更喜欢函数式的。我就是这样。为什么?因为我发现函数型风格往往要比命令式风格的代码更简洁,且更不易出错。函数式风格的代码通常层次更高,这使得它编写起来更快,阅读也更为容易。举个例子,看看下面这段确定一个字符串是否包含大写字符的 Java 代码。

boolean nameHasUpperCase = false; // 这是Java
for (int i = 0; i < name.length(); ++i) {

    if (Character.isUpperCase(name.charAt(i))) {

        nameHasUpperCase = true;

        break;

    } 

}

这里的命令式风格是很明显的,因为 nameHasUpperCase 变量被重新赋值会给 loop 循环带来副作用, loop 是通过字符串中的字母索引进行迭代的。在 Java 你还可以以更为简洁的方式得到相同的结果,像下面这样:

boolean nameHasUpperCase = !name.toLowerCase().equals(name);

这一行 Java 代码展现出一种更为函数化的风格,因为它转换不可变数据: name 这个字符串被转换为另外一个全部字母都是小写的字符串,然后值被转换为布尔结果。此外, nameHasUpperCase 这个变量被初始化了,但仅限于这一小块代码里,而没有被重新赋值。如果该变量为最终值的话,它的函数化就会更为清晰。

在 Scala 里面,你可以写出跟以上两个例子类似的代码,不过更为理想的编写方式是像下面这样的:

val nameHasUpperCase = name.exists(_.isUpperCase)

nameHasUpperCase 变量被定义为 val ,即可被初始化但不能被重新赋值的变量(类似于 Java 里面的最终变量)。甚至于尽管本例中并无显式的类型标注, Scala 的类型推断机制也会给 nameHasUpperCase 赋予 Boolean 类型。 exists 方法在对象集合中迭代,并依次将每个元素传递给函数对象。在这里, name 字符串被视为字符集合,因此 exists 会把字符串的每一个字符都传递给该函数。 _.isUpperCase 的语法是 Scala 里的一种函数显式声明( function literal ),是一种编写少量代码就可以到处传递和调用的速写方式。下划线代表该函数的唯一参数。因此你可以把下划线视为每次该函数被调用时待填的空白。如果 exists 方法发现该函数因被传递的字符中的其中一个而返回 true— 比如说,其中一个字符是大写的 — 而返回 true 。否则就返回 false 。

尽管最后的这个单行代码对于某些不熟悉Scala的人来说像是天书,只要你了解了 Scala,你就能一眼看出代码的目的。相反,其他的两个版本却要费上一点功夫去研究一下。另外需要注意的一点不同是命令式例子中潜在的偏移错误,因为你必须显式地指出迭代的上标。在函数化的版本里这种错误不会产生,在这种方式下,函数化版本相对而言不易出错。

最后,我想指出的是我转向 Scala 的时候并没有“彻底函数化”。尽管我已经发现通常大部分情况下函数化风格的代码来得更为简洁、明晰,更不易出错,我还发现有时候命令式风格也可带来更为清晰和简洁的代码。在那种情况下,我就会使用命令式的。 Scala 允许我方便地应用函数式和命令式的风格,结合使用此二者,我就能找到写出清晰代码的最佳方式。

函数式编程和命令式编程简介

什么是函数式编程?(参考资料:《征服RIA:基于JavaScript的Web客户端开发》第8章JavaScript函数对象 在数学领域,函数是一种关系,这种关系使一个集合里的每一个元素对应到另一个集合里的唯一元素。函数是将唯一的输出值赋予每一输入的"法则"。这一"法则"可以用函数表达式、数学关系,或者一个将输入值与输出值对应列出的简单表格来表示。函数最重要的性质是其决定性,即同一输入总是对应同一输出(注意,反之未必成立)。从这种视角,可以将函数看做"机器"或者"黑盒",它将有效的输入值变换为唯一的输出值。通常将输入值称做函数的参数,将输出值称做函数的值。 《Why Functional Programming Matters》的作者John Hughes 说明了模块化是成功编程的关键,而函数编程可以极大地改进模块化。在函数编程中,编程人员有一个天然框架用来开发更精练的、更小的、更简单的和更一般化的模块,然后将它们组合在一起。函数式编程的基本特点是: 丰富的数据类型; 函数是运算元; 在函数内保存数据; 函数内的运算对函数外无副作用。 函数式编程只描述在程序输入上执行的操作,不必使用临时变量保存中间结果。重点是捕捉"是什么以及为什么",而不是"如何做"。与将重点放在执行连续命令上的过程性编程相比,函数式编程的重点是函数的定义而不是状态机(State Machine)的实现。是一种强调表达式的计算而非命令的执行的一种编程风格。表达式是用函数结合基本值构成的,它类似于用参数调用函数(函数式的优美的说明可见《Functional Programming For The Rest of Us》)。 什么是命令式编程?(参考资料:维基百科) 命令式编程,是种描述电脑所需作出的行为的编程典范。几乎所有电脑的硬体工作都是命令式的;几乎所有电脑的硬体都是设计来执行机器码,使用命令式的风格来写的。较高阶的命令式编程语言使用变数和更复杂的语句,但仍依从相同的典范。食谱和行动清单,虽非电脑程式,但与命令式编程有相似的风格:每步都是指令,有形的世界控制情况。因为命令式编程的基础观念,不但概念上比较熟悉,而且较容易具体表现于硬体,所以大部分的编程语言都是命令式的。 原文:How Scala Changed My Programming Style 作者:Bill Venners

【相关阅读】 Scala的类型系统 比Java更灵活 Java程序员,你为什么要关注Scala Scala创始人:创造比Java更好的语言 Java以外的选择 Scala编程语言简介 Java之外,选择Scala还是Groovy?

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java后端技术栈

Java提供的排序算法是怎么实现的?快排?

前几天整理的一套面试题,其中有一个问题就是Java的JDK中我们见到的Collections.sort()和Arrays.sort()这两个排序算法的实现方式是...

20030
来自专栏软件开发 -- 分享 互助 成长

简单工厂模式

一、简单工厂模式的相关概念: 1、定义:简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式。 其核心思想就是有一...

19290
来自专栏醒者呆

融会贯通——最常用的面向对象设计原则“合成复用原则”

复用一个类的时候,多使用对象的组合/聚合的关联关系,而不是继承。 之前提到的“依赖倒转原则”,是以里氏代换原则为基础的实现开闭原则目标的手段,这一条路线涉及到的...

31980
来自专栏贾老师の博客

浮点数存储格式

14330
来自专栏C语言C++游戏编程

C语言讨论象棋将帅问题,代码短又美!

问题的本身并不复杂,只要把所有A、B 互相排斥的条件列举出来就可以完成本题的要 求。由于本题要求只能使用一个变量,所以必须首先想清楚在写代码的时候,有哪些信息需...

14530
来自专栏企鹅号快讯

列表和元组有什么区别

如果有了解过python中的列表和元组,你可能会知道相对于列表,元组是不可变的,也就是说元组中的数据不能随意更改。除了列表是用中括号表示而元组是用小括号表示之外...

26270
来自专栏一个会写诗的程序员的博客

第8章 函数式编程(FP) 《Kotin 编程思想·实战》

8.1 函数式编程概述 8.1.1 面向对象编程OOP特征 8.1.2 函数式编程FP特征

9410
来自专栏木制robot技术杂谈

谈一谈Python中str()和repr()的区别

前言 在学习BeautifulSoup文档的时候发现了一个以前不常见的Python内建函数repr(),带着好奇对这个内建函数进行了一番搜索和学习。 总结 s...

39140
来自专栏老九学堂

零基础学Java第三讲变量

如何掌握了变量这个语法?看看微视频中对应的知识点的讲解。 别走开,下面有干货哦! 1了解什么是变量?变量如何使用? 2会使用常用的数据类型 任何编程语言的语...

31750
来自专栏趣谈编程

选择排序

面试官: 聊聊选择排序 选择排序是一种简单直观的算法,今天我们聊聊选择排序的思想,代码以及复杂度 排序思想 一天,小一尘和师傅下山去了,在集市中路经一个水果摊,...

34380

扫码关注云+社区

领取腾讯云代金券