Martin Odersky访谈录所思

简介: 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范儿。

相关文章
总结模板集锦
对自己本次解决的思路是否正确?对应方案是否可行?是否还有更好方案?
|
机器学习/深度学习 存储 分布式计算
软件工程大师 Martin Fowler 谈机器学习持续交付(下)
机器学习应用程序在我们的行业中变得越来越流行,但是与更传统的软件(例如: Web 服务或移动应用程序)相比,开发、部署和持续改进它们的过程更加复杂。 它们会在三个轴上发生变化:代码本身、模型和数据。 他们的行为通常很复杂且难以预测,而且他们更难测试、更难解释、更难改进。 机器学习的持续交付 (CD4ML) 是将持续交付原则和实践引入机器学习应用程序的学科。
|
机器学习/深度学习 存储 算法
软件工程大师 Martin Fowler 谈机器学习持续交付(上)
机器学习应用程序在我们的行业中变得越来越流行,但是与更传统的软件(例如: Web 服务或移动应用程序)相比,开发、部署和持续改进它们的过程更加复杂。 它们会在三个轴上发生变化:代码本身、模型和数据。 他们的行为通常很复杂且难以预测,而且他们更难测试、更难解释、更难改进。 机器学习的持续交付 (CD4ML) 是将持续交付原则和实践引入机器学习应用程序的学科。
|
机器学习/深度学习 监控 Kubernetes
软件工程大师 Martin Fowler 谈机器学习持续交付(中)
机器学习应用程序在我们的行业中变得越来越流行,但是与更传统的软件(例如: Web 服务或移动应用程序)相比,开发、部署和持续改进它们的过程更加复杂。 它们会在三个轴上发生变化:代码本身、模型和数据。 他们的行为通常很复杂且难以预测,而且他们更难测试、更难解释、更难改进。 机器学习的持续交付 (CD4ML) 是将持续交付原则和实践引入机器学习应用程序的学科。
|
算法 程序员 测试技术
【人月神话】01 人月神话
【人月神话】01 人月神话
200 0
【人月神话】01 人月神话
|
机器学习/深度学习 存储 编解码
IEEE年度大奖揭幕!华人科学家,Yang-Kieffer算法之父杨恩辉斩获Eric E.Summer奖
IEEE 宣布将Eric E. Sumner奖发给加拿大滑铁卢大学终身教授杨恩辉教授,以表彰他在视频压缩领域的贡献。
345 0
IEEE年度大奖揭幕!华人科学家,Yang-Kieffer算法之父杨恩辉斩获Eric E.Summer奖
|
机器学习/深度学习 JavaScript 前端开发
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(下)
刚刚,2020年图灵奖揭晓!影响了数代人的「龙书」作者——阿尔佛雷德·艾侯 (Alfred Aho)和杰弗里·戴维·乌尔曼(Jeffrey David Ullman)获奖。
207 0
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(下)
|
算法 编译器 程序员
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(上)
刚刚,2020年图灵奖揭晓!影响了数代人的「龙书」作者——阿尔佛雷德·艾侯 (Alfred Aho)和杰弗里·戴维·乌尔曼(Jeffrey David Ullman)获奖。
210 0
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(上)
|
算法 Unix 编译器
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(中)
刚刚,2020年图灵奖揭晓!影响了数代人的「龙书」作者——阿尔佛雷德·艾侯 (Alfred Aho)和杰弗里·戴维·乌尔曼(Jeffrey David Ullman)获奖。
475 0
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(中)
|
SQL 人工智能 自动驾驶
Jeff Dean只是冰山一角!盘点劈柴哥的17个「贤内助」
最近,Business Insider披露了谷歌内部最新的组织结构图,CEO皮采的核心团队成员曝光,其中不仅包括谷歌AI负责人Jeff Dean,还有众多资深高管,一起来看看谷歌这个1.3万亿美元市值的科技巨头的掌舵团队吧。
224 0
Jeff Dean只是冰山一角!盘点劈柴哥的17个「贤内助」