Martin Odersky访谈录所思

ThoughtWorks的「TW洞见」在4月发布了对Scala之父Martin Odersky的访谈。Odersky的回答显得言简意赅,仔细分析,仍然能从中收获不少隐含的信息(虽然可能是负面的信息)。

提问的中心主要是语言之争。Scala是一门极具吸引力的语言,似乎天生具备一种气质,轻易能够吸粉,但招黑的能力也不遑多让。它似乎是从象牙塔里钻研出来的,但又在许多大型项目和产品中得到了实践。有人转向了她,又有人之后背弃了它。如果说Ruby的助力是Rails,那么推动着Scala在社区中成长的,其实到处可见Spark的影子。

然而,一个尴尬的现状是,Spark的许多源代码并没有遵循Scala推崇的最佳实践。Odersky对此的解释是:

Spark的API设计是和Scala 集合类设计是一致的函数式风格,里面具体的实现为了追求性能用了命令式,你可以看到Scala集合里面的实现函数为了性能也用了很多var。

这或许是Scala采用多范式的主要原因吧。虽然Scala借鉴了不少函数式语言的特性,例如Schema和Haskell,但Scala并没有强制我们在编写代码时严格遵守FP的原则。我们需要在OO与FP之间画一条线。在代码的细节层面,Scala要求我们尽力编写没有副作用(引用透明),提供组合子抽象的函数式风格代码;然而在一些场景下,又允许我们让位于OO的统治。

Scala属于语言中的“骑墙派”,只要你足够高明,就能够在OO与FP中跳转如意,怡然自得,如鱼得水。所谓“骑墙”,反倒成了具有超强适应能力的“左右逢源”,何乐而不为?

Odersky在访谈中推荐了Databricks给出的Scala编码规范,还有lihaoyi的文章Strategic Scala Style: Principle of Least Power。

如果我们阅读Databricks给出的编码规范,会发现Databricks为了性能考虑,更倾向于采用命令式方式去使用Scala,例如,规范建议使用while循环,而非for循环或者其他函数转换(map、foreach)。

val arr = // array of ints
// zero out even positions
val newArr = list.zipWithIndex.map { case (elem, i) =>
  if (i % 2 == 0) 0 else elem
}

// This is a high performance version of the above
val newArr = new Array[Int](arr.length)
var i = 0
val len = newArr.length
while (i < len) {
  newArr(i) = if (i % 2 == 0) 0 else arr(i)
  i += 1
}

然而就我个人的习惯,更倾向于前者(使用zipWithIndex结合map),它采用更加简洁的函数式风格。鱼与熊掌,不可兼得!这是一个问题!

规范从可读性角度考虑,不建议使用Monadic Chaining。例如,下面的代码使用连续两个flatMap:

class Person(val data: Map[String, String])
val database = Map[String, Person]()
// Sometimes the client can store "null" value in the  store "address"

// A monadic chaining approach
def getAddress(name: String): Option[String] = {
  database.get(name).flatMap { elem =>
    elem.data.get("address")
      .flatMap(Option.apply)  // handle null value
  }
}

规范建议,改写为更具有可读性的方式:

// A more readable approach, despite much longer
def getAddress(name: String): Option[String] = {
  if (!database.contains(name)) {
    return None
  }

  database(name).data.get("address") match {
    case Some(null) => None  // handle null value
    case Some(addr) => Option(addr)
    case None => None
  }
}

虽然利用模式匹配(Pattern Match)确实是很好的Scala实践,但就这个例子而言,其实Monadic Chaining的方式可以用for comprehension来改写。非常简洁,可读性极佳:

for {
    elem <- database.get(name)
    addr <- elem.data.get("address")
} yield addr

那么,这样的规范是否是好的Scala实践呢?Odersky用“保守”一词来评价这一规范,不知其本意如何?

lihaoyi的文章Strategic Scala Style: Principle of Least Power不是一个规范,而是一份Scala最佳实践。内容包括对不变性与可变性、接口设计、数据类型、异常处理、异步、依赖注入的分析与建议。值得一读。

Martin Odersky言简意赅地给出了两个编写Scala代码的原则:

  • 尽量用能力弱的功能;
  • 给中间步骤命名。

对于第一点,我个人的理解是在使用Scala特性的时候,要注意克制,不要去玩弄Scala语法中那些奇技淫巧,从而让代码变得晦涩难懂。Twitter的部分工程师之所以对scala抱有怨言,多数吐槽点就是在代码的可读性与维护性方面。

第二点同样是为了解决此问题。Twitter的文档Effective Scala用例子阐释了为中间步骤命名的重要性。如下例子:

val votes = Seq(("scala", 1), ("java", 4), ("scala", 10), ("scala", 1), ("python", 10))
val orderedVotes = votes
  .groupBy(_._1)
  .map { case (which, counts) => 
    (which, counts.foldLeft(0)(_ + _._2))
  }.toSeq
  .sortBy(_._2)
  .reverse

这样的代码虽然简洁,却不能好好地体现作者的意图。如果恰当地给与中间步骤命名,意义就更加清楚了。

val votesByLang = votes groupBy { case (lang, _) => lang }
val sumByLang = votesByLang map { case (lang, counts) =>
  val countsOnly = counts map { case (_, count) => count }
  (lang, countsOnly.sum)
}
val orderedVotes = sumByLang.toSeq
  .sortBy { case (_, count) => count }
  .reverse

Odersky在访谈中谈到了一些对未来Scala的规划,包括Tasty与Dotty,前者是为了解决Scala二进制不兼容问题,Dotty则是为Scala提供新的编译器。然而,Odersky的回答令人黯然,二者的真正推出还需要等待几年时间。

几年时间啊!再过几年,Scala会否成为明日黄花呢?至少Java的进化趋势已经开始威胁Scala了。而JVM的演进是否又会进一步为Scala的演进造成障碍呢?如果还要考虑版本兼容问题,Scala的未来版本境遇堪忧啊。想想我都为Odersky感到头痛呢。

可是Scala又不能离开JVM,否则Scala与Java兼容带来的福利就荡然无存了。庞大的Java社区一直是Scala可以汲取的资源呢。Scala会否成也JVM,败也JVM呢?

坦白说,这个访谈没有提供太多Scala的营养(不知是否翻译的问题),总觉得Odersky在面对某些有关语言的尖锐问题时,显得闪烁其词。虽然Odersky搬出了沃尔沃美国、高盛、摩根斯坦利来压阵,却反给我底气不足的感觉。Scala不好的部分还是太多了,它会妨碍我们对Scala做出正确地判断。Scala待解决的问题仍然太多了,lightbend任重而道远。归根结底,从一开始,Odersky没有对Scala特性做出具有控制力的规划,缺乏收敛,导致许多feature良莠不齐,败坏了Scala的名声。

还好有一个Spark,是Spark拯救了Scala。可惜,Spark的编码规范却不具备Scala范儿。

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2016-05-11

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序人生

抽象的能力

人类的智商从低幼逐渐走向成熟的标志之一就是认识和运用数字的能力。当我们三四岁的时候,数数虽然能够熟练地对一百以内的数字随心所欲地倒背如流,但数字对孩童时代的我们...

35070
来自专栏take time, save time

[细节决定B度]之二分搜索也不易啊

     实事求是的说二分搜索是我学习算法的时候学的最好,理解的最透彻,能够当时就写出代码的的算法。事到如今,就如我可以分分钟写出hello world一样,我...

31960
来自专栏Alan's Lab

JS 变量提升

问到 JS 一些细节问题的时候发挥比较糟糕,有些是知道反应得太慢,有些是压根没接触过,还是积累的太少了。这篇的 JS 变量提升问题就是从没有接触过的,网上一搜一...

32220
来自专栏大数据钻研

由浅入深的前端面试题 和矫情的“浪漫主义”诗句

好吧,我承认太标题党了,这篇文章是通过一道前端面试题引出的纯技术讨论。我先要矫情无比的从中外诗歌说起。 传统的佛学经典里,被世人熟知的有这样一句话:“一花一世界...

352100
来自专栏ACM算法日常

逮捕罪犯(树)- HDU 3069

Country ALPC has n cities, and the cities are connected by undirected roads. Fur...

9310
来自专栏tkokof 的技术,小趣及杂念

编个“猜数字”玩玩

一日午晌,顿觉百无聊赖,阵阵哈切之余,竟忆起儿时游玩之小游戏,名曰“猜数字”,此物规则甚是简单,游玩之时仅需猜测一四位数字,接着便可得到相应之正误结果,然后依此...

5810
来自专栏小樱的经验随笔

BZOJ 2222: [Cqoi2006]猜数游戏【神奇的做法,傻逼题,猜结论】

2222: [Cqoi2006]猜数游戏 Time Limit: 20 Sec  Memory Limit: 259 MB Submit: 604  Solve...

29160
来自专栏Java社区

开发团队中的两种编程高手

12630
来自专栏海天一树

NOIP普及组初赛题型分析

初赛的考察内容的一部分是计算机的基础知识,比如进制转换,工作原理,算法原理、历史事件名人等。这些对于大部分第一次参加noip的同学来说应该比较陌生,这样的知识只...

11020
来自专栏逸鹏说道

重温数据结构系列随笔:数据结构的基本概念

现在项目已经踏上正轨,有不少时间可以用来学习,昨晚发现柜子里那本大学时候啃过无数遍的(数据结构 C语言版),那真的无限感叹啊,初恋女友啊,大学回忆啊都涌上心头。...

30040

扫码关注云+社区

领取腾讯云代金券