知乎上有道问题很有意思:如何看待John A De Goes正式宣布离开Scala社区?

John是Scala社区的长期布道者,也是ZIO项目的创始人。ZIO被视为继ScalaZ、Cats之后的下一代Scala的函数式编程(FP)框架,提供了性能更为优秀的用户态协程。ZIO社区的野心极大,与诸多第三方服务实现了生态对接。其目标可与Java社区的Spring相提并论 (当然了,这也是用户态协程的缺点,为了让调用第三方组件的代码也能被用户态实协程按需挂起/恢复执行,必须侵入对应的SDK)

这样一位标杆式的人物,不再看好Scala未来的发展,选择退出。其背后的原因值得深思。

我的Scala经历

回顾我的工作经历,先先后后在三个项目上用过Scala。每个项目对Scala的使用也竟然都各有特点。

首先是在一个客户项目。尽管客户仅仅需要一个MVP产品,由多个微服务组成,但他们还是选择了使用Scala + Cats-IO + http4s来实现。更早的语言选型Ruby,因为动态类型的问题,代码维护成本较高。而新项目的选择Scala拥有极为强大的类型系统。这是直接“从地下室一飞冲天”的感觉。

这位客户团队的FP素养极高。先期在其他项目已经应用了FreeMonad模式一段时间,而新项目选择用tagless final模式。在MVP实现阶段,也是严格遵循FP的理念,一丝不苟的讨论类似:“函数纯不纯”,“side effect怎么处理”这样的问题。甚至打印日志也被当作副作用,外推到更外层,实现了一个事件驱动的Monad,保证了内部处理逻辑的函数足够“纯”。

总的来说,项目代码的实现是纯粹的FP风格

第二次,是在一家国内大型上市公司,为多家世界级客户提供服务。团队主要是为维护一些Spark离线处理任务。尽管团队选择了Scala,但这个纯粹是为了工程需求服务。能够感受到团队在Scala的FP上了解甚少。Lead也曾经问出过“怎么样让Scala的Int先初始化为null”这样的问题。

代码中,除了valdef还能让人感受到一丝Scala语法的存在,其字里行间充斥着null的判断逻辑,还有随处可见在的Java Integer类型(因为可以先初始化为null),让那一股浓厚的Java屎山之味道扑鼻而来。

总的来说,用Scala写Java,连Java的Integer都忘不了。

第三次,是在一家外企。这家公司使用Scala也是维护后端服务。但与第一位对FP的执着不同,该公司做选择时更多的照顾着工程要求。一些按FP条条框框来说需要讨论的问题,如果不是突破底线,倒也就忽略了。

总的来说,工程派

对Scala工程实践的思考

缺点

我真是太能杠了,从缺点开始。

同一个语言,在三个项目上实践,竟然有三种不同的感受。这可能要“归功于”Scala对多编程范式的支持了。既有ADT,for表达式这种FP利器,同时因为要寄生在Java生态中,还对JDK有很不错的兼容性。

但这对企业工程实践来说却未必好。因为,过于灵活的编程模式,造成了程序员发展的多种可能性。在一个项目上培养出来的程序员,在另一个项目上可能需要重新适应新的编程要求,尽管他们都叫Scala程序员。这在某种意义上讲,提高了企业培养成本和招聘风险

还有一点更像是FP本身的问题。相比传统的面向对象或者过程式代码,FP给代码增加了新的抽象层次。考虑公司里的程序员:

  • 能从大段的逻辑中,抽离出共有的变量作为函数入参,或者公共变量,已经是很不错的了,成功剔除了一种代码的坏味道。
  • 假如从一段逻辑中,观察出共有的函数行为,并将其抽离成为函数类型的入参,简直“难于上青天”。

简单来讲,FP思想曲高和寡,对程序员的心智要求提高了,不是谁都能自如应用得了的。

此外,程序员除了讨论业务逻辑,还要花时间讨论代码设计本身是否满足FP理念。抛开技术热情,我们应该去思考一下这些讨论能够带来多少工程价值的增量提升?权衡之后,才能得到明智的选项。(毕竟,工程实践依托于市场经济。“花小钱办大事”,是其内生动力)

要知道,所有FP语言还没有一个能真正在工程领域得到大规模应用的。Haskell,Clojure这样纯FP语言,2010年之后逐渐归隐于学术界。Erlang,Elixir也只是依靠BEAM的独门绝技存活于特定领域中。

作为对比

考虑一下,真正的工程界常青树Java和冉冉升起的“大道至简”语言Golang。这两种语言,不管新手老手,大家写出来的代码都长差不多一样。所以A公司培养出来的Java程序员,Golang程序员,B公司可以无脑招入。只要不是简历造假,进来就能干活。一个月后就能熟练的干活。哪个公司不喜欢?

优点

Scala当然有很多优点,不然也不会有那么多大公司,甚至还有一些商业银行,都愿意采用。

首先是强大的类型系统。强大的类型推导,在保证了出品质量的稳定性的同时,也免去了程序员手写复杂类型的繁琐。这也是很多Java程序员初写Scala所感觉到的爽。基于此,Scala生态可以出现一些能够利用复杂类型的FP框架,如ScalaZ,Cats-effect,ZIO等。能够进一步保证程序的正确性。

举个简单的例子,Java里的RuntimeException可以不捕获,也可以捕获。假如需要捕获,而抛出的位置埋藏得很深,程序员有可能忘了捕获。也有可能为了防止忘记捕获,程序员选择了滥用捕获。这两种情况都不好。而Scala提供了Either<L, R>泛型,可以以特别的返回类型告知程序员或有异常返回,必须处理。由程序员决定继续向上返回,还是立即处理。

其次是Scala代码更为简洁。举个例子,case class/tuple的解构:

val a: Int = ???
val b: Int = ???

(a, b) match {
    case (1, 101) => ???
    case (2, 202) => ???
    case (1, 202) => ???
    case (2, 101) => ???
    case _ => ???
}

这段简单的逻辑会根据ab两个变量值的组合来执行后续逻辑。如果使用Java来写,那会很啰嗦。众所周知,代码写越多,越容易犯错。Scala的这种简洁的形式,同样可以减轻代码的维护负担。

再杠一下

基本的正确性可以用测试来保证。单元测试、自动化回归测试等等测试体系,一样可以保证任何语言的实现代码的正确性。

类型系统强大的也不只有Scala一家。C++/Rust的类型系统也都名声在外。Rust有Result<T, V>类型,C++ 23也引入了expected<T>类型。而且语言没提供,用户也可以自己实现啊。例如Google出品的C++库Abseil就实现了StatusOr<T>类型,Meta的folly库提前实现了expected<T>类型。

而Scala的简洁,有时候会成为一个减分项。因为太简洁了,有些逻辑因为时间太久,原作者都有可能看不懂了。这就需要,程序员在实际工作中依据经验做出前瞻性的选择,以保证代码的可维护性。然而有丰富经验的Scala程序员并不多。这个就有点“鸡生蛋、蛋生鸡”的意思了。

仔细留意一下便可发现,Scala的优点都是锦上添花型的,没有护城河

我对Scala的态度

毫无疑问,我对Scala是喜爱的。Scala为我打开了FP的大门,尽管没能用得更多,但也让我对编程有了更深刻的理解。

但是,我是不想在工作中再用Scala了。一千个人能写出一千种“哇哦”的Scala的代码。看多了心累。

Scala3的未来多半是Haskell

尽管Scala社区在升级Scala2/3的时候,口口声声说不会再走Python2/3的覆辙。但是,这次恐怕还是冲着旧车印飞奔而去。

Scala3推出一两年了。官方的Akka对Scala3的支持都还是experimental阶段,不得不让我对Scala3的现状感到担忧。而且,Akka去年换用BSL协议,开始收割韭菜,更是吓跑了一众用户。

要我看,Scala未来很可能会是Haskell那样的小众语言。

还有一个假如

FP语言其实更为适合基础设施领域。因为基础设施程序员的心智更为强大,更容易接纳FP理念。同时FP对正确性的保证对基础设施领域也很有诱惑力。

所以,如果有一天Scala Native能够解决:

  • 性能问题:至少也要达到Golang 1.16+的水准
  • 生态问题:能够方便的与C/C++/Rust生态对接

那Scala想必将会有一片新的天地。