前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编程实践 | Scala亮瞎Java的眼(一)

编程实践 | Scala亮瞎Java的眼(一)

作者头像
张逸
发布2018-03-07 15:55:29
7230
发布2018-03-07 15:55:29
举报
文章被收录于专栏:斑斓斑斓

这是我在11月15日成都OpenParty分享的一个题目,确有标题党的嫌疑。Scala自然不是无所不能,Java也没有这么差劲,我只希望给Java程序员提供另外一条可能的选择。在Java 8后,我对Java的怨念已经没有那么强烈了,然而,Scala的优势仍然存在。

比较Java 8,我重点讲解了Scala的如下优势:

  • 简洁代码
  • 支持OO与FP
  • 高阶函数
  • 丰富的集合操作
  • Stream支持
  • 并发支持

简洁代码

Scala提供的脚本特性以及将函数作为一等公民的方式,使得它可以去掉不少在Java中显得冗余的代码,例如不必要的类定义,不必要的main函数声明。Scala提供的类型推断机制,也使得代码精简成为可能。Scala还有一个巧妙的设计,就是允许在定义类的同时定义该类的主构造函数。在大多数情况下,可以避免我们声明不必要的构造函数。

Scala还提供了一些非常有用的语法糖,如伴生对象,样例类,既简化了接口,也简化了我们需要书写的代码。例如如下代码:

case class Person(name: String, age: Int) val l = List(Person("Jack", 28), Person("Bruce", 30))

这里的List和Person都提供了伴生对象,避免再写冗余的new。这种方式对于DSL支持也是有帮助的。Person是一个样例类,虽然只有这么一行代码,蕴含的含义却非常丰富——它为Person提供了属性,属性对应的访问器,equals和hashcode方法,伴生对象,以及对模式匹配的支持。在Scala 2.11版本中,还突破了样例类属性个数的约束。由于样例类是不变的,也能实现trait,因而通常作为message而被广泛应用到系统中。例如在AKKA中,actor之间传递的消息都应该尽量定义为样例类。

支持OO与FP

将面向对象与函数式编程有机地结合,本身就是Martin Odersky以及Scala的目标。这二者的是非,我从来不予以置评。个人认为应针对不同场景,选择不同的设计思想。基于这样的思想,Scala成为我的所爱,也就是顺其自然的事情了。

演讲中,我主要提及了纯函数的定义,并介绍了应该如何设计没有副作用的纯函数。纯函数针对给定的输入,总是返回相同的输出,且没有任何副作用,就使得纯函数更容易推论(这意味着它更容易测试),更容易组合。从某种角度来讲,这样的设计指导思想与OO阵营中的CQS原则非常一致,只是重用的粒度不一样罢了。

我给出了Functional Programming in Scala一书中的例子。如下代码中的declareWinner函数并非纯函数:

object Game { def printWinner(p: Player): Unit = println(p.name + " is the winner!") def declareWinner(p1: Player, p2: Player): Unit = if (p1.score > p2.score) printWinner(p1) else printWinner(p2) }

这里的printWinner要向控制台输出字符串,从而产生了副作用。(简单的判断标准是看函数的返回值是否为Unit)我们需要分离出专门返回winner的函数:

def winner(p1: Player, p2: Player): Player = if (p1.score > p2.score) p1 else p2

消除了副作用,函数的职责变得单一,我们就很容易对函数进行组合或重用了。除了可以打印winner之外,例如我们可以像下面的代码那样获得List中最终的获胜者:

val players = List(Player("Sue", 7), Player("Bob", 8), Player("Joe", 4))

val finalWinner = players.reduceLeft(winner)

函数的抽象有时候需要脑洞大开,需要敏锐地去发现变化点与不变点,然后提炼出函数。例如,当我们定义了这样的List之后,比较sum与product的异同:

sealed trait MyList[+T] case object Nil extends MyList[Nothing] case class Cons[+T](h: T, t: MyList[T]) extends MyList[T] object MyList { def sum(ints: MyList[Int]):Int = ints match { case Nil => 0 case Cons(h, t) => h + sum(t) } def product(ds: MyList[Double]):Double = ds match { case Nil => 1.0 case Cons(h, t) => h * product(t) } def apply[T](xs: T*):MyList[T] = if (xs.isEmpty) Nil else Cons(xs.head, apply(xs.tail: _*)) }

sum与product的相同之处都是针对List的元素进行运算,运算规律是计算两个元素,将结果与第三个元素进行计算,然后依次类推。这就是在函数式领域中非常常见的折叠(fold)计算:

def foldRight[A, B](l: MyList[A], z: B)(f: (A, B) => B):B = l match { case Nil => z case Cons(x, xs) => f(x, foldRight(xs, z)(f)) }

在引入了foldRight函数后,sum和product就可以重用foldRight了:

def sum(ints: MyList[Int]):Int = foldRight(ints, 0)(_ + _) def product(ds: MyList[Double]):Double = foldRight(ds, 0.0)(_ * _)

在函数式编程的世界里,事实上大多数数据操作都可以抽象为filter,map,fold以及flatten几个操作。查看Scala的集合库,可以验证这个观点。虽然Scala集合提供了非常丰富的接口,但其实现基本上没有超出这四个操作的范围。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2014-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 逸言 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档