首发于硅谷直说
【从入门到放弃】Scala

【从入门到放弃】Scala

先事先声明一下,“从入门到放弃”是一个系列。这个系列是我今年的一个小实验:用闲暇时间学习一个技术,学它一个月,然后写一个感受,下个月再换。与其说是“放弃”,不如说是“不恋战”。毕竟学的断断续续,并没有真正入门,也没有真正放弃。

对于Scala,我之前虽然没有系统学过,但是工作中用到过两次。

第一次是在雅虎工作的时候。我们当时发现,雅虎新闻的评论区经常有垃圾信息。一个新闻,底下一群人喊口号、或者互相言语攻击:MAGA啊、libtard啊、trumptard啊、snowflake之类的。而且这些没有价值的评论经常被一群人(或者僵尸号)点赞。如果按照点赞数量排序的话,这些垃圾评论就会顶到最高。

为了解决这个问题,我们当时想在评论排序上引入相关性。如果评论和文章相关,那相关性就高。如果把相关性和点赞数量相结合,评论区至少不会太恶心。

怎么算出相关性呢?我们先要做一个评论相关性的数学模型,从新闻文本里面抽取一些主题,然后每个评论抽取一些主题,如果主题贴近,这就说明评论和新闻相关。“高相关性”的评论就会被顶到高处,而低相关性的评论会放到靠后的位置。

当时这个数学模型是一个实习生写的,写的时候用的是Scala。实习生写完以后就上学去了,没有人接管这个项目。我当时正在做评论区优化,于是就把它接了下来。刚刚接手的时候我真的是完全不懂Scala,简单学了一晚上,第二天就跟赶鸭子上架似的写代码去了。经过一番折腾,我终于磕磕绊绊地把这个数学模型写成了一个Web API,应用到了评论区。

这是今天的雅虎主页头条新闻的评论区,基本上没有喊口号了,也没有互相人身攻击了


第二次接触Scala是在Stripe工作的时候。Stripe是个做API的公司。它的API的权限管理主要是用API key做的。我的任务就是找到所有存储系统的API key,把它们划掉。

Stripe的API流量很大,每秒成千上万的RPS。Stripe成立于2009年,我在2019年开始清除数据,那么就有十年的数据。

10年 == 315,569,520秒

假设每秒request有1000个,那么一共有315,569,520,000条数据需要清除。三千多亿条数据,一个脚本是干不过来的。于是我借助了一个开源工具Summingbird,它可以用分布式的方法做数据处理。而这个工具只支持Java和Scala。

这两个项目如今都已经做完了,但我总觉得对Scala不甚了解。于是这个月初我开始系统地按照官方教程把它学了一遍。

官方教程链接:

Introductiondocs.scala-lang.org图标

我为了更好地理解官方教程,我把一些教程的例子拷贝下来自己把玩:

quzhi1/ScalaPlaygroundgithub.com图标

学完Scala以后我的第一感受就是它是个Java和Scripting language结合的产物。Java是一个很古典的语言,静态类型和面向对象这些设计真的很经典。然而Java仍有一些需要改进的地方。

第一个地方就是满足不了今天才出现的奇怪需求。最典型的就是Lambda。Java发明的时候不太需要考虑如何把一个function当做value用的情况,而今天这种情况特别多。另一个需求就是stream programing。在Java 8以前,Java本身是做不了stream programming的。Java 8虽然支持了这个功能,但是还是很难用。大家还是该用for loop用for loop,旧习难改。这两个需求Scala都满足了。

第二个地方就是Java很危险。我说的危险不是它容易漏东西,而是说Java初学者容易把Java写出错。我刚刚接触Java的时候,继承的逻辑都是混乱的,随意声明class field,胡乱写Generic。本来可以compile time捕捉的bug,最后在run time才能看到。这些给程序员的坑Scala都尽量避免了。

在我看来,Scala就是一个威力加强版的Java。

我喜欢Scala很多设计:

第一个就是case class。它解决了Java里面clone的问题。Java容易分不清楚我和clone之间的关系。我的双胞胎到底是不是我自己呢?Java为了解决这个问题,不得不引入了一个Comparable interface和equals method。而Scala就没这么多顾虑。

第二个是object。Java的一些class需要保证singleton,而singleton在Java里面写起来很别扭,很多人干脆就不写了。Scala的object很好写,这方面比Java安全得多。

第三个是generic。在我见过的语言里,只有Scala把generic做到了极致。function可以有generic,class可以generic,class继承的时候也可以generic。而且generic可以定义上下限,定义继承关系,只要想不到没有做不到。

当然Scala也有一些我不太不习惯的设计。

有些名字我觉得起的别扭。比如Unit,英语应该是单位的意思,结果在Scala里面它代表void。还有一个例子:Nil。Nil在英语里是不存在的意思,所以很多语言nil就是null。结果Scala里面nil不是null,而是个空集合(empty collection)。这些命名方式真的很令人困惑。

Scala有implicit parameter和implicit value。这个设计我觉得特别像C和C++时代的global variable,感觉特别危险。不过这方面我的理解能力有限,它这么设计或许有一些特别的原因。

感受就是这么多,下面是我的学习笔记:

Basics

  • Like JavaScript, val can not be re-assigned, var can.
  • Methods is "functions of other languages"
  • Functions is basically anonymous functions
  • Unit is basically void
  • Instances of case classes are immutable, and they are compared by value (unlike classes, whose instances are compared by reference).
  • You can instantiate object without using class
  • Traits are basically interfaces

Types

  • Unit is a value type which carries no meaningful information. There is exactly one instance of Unit which can be declared literally like so: ().
  • If Scala is used in the context of a Java runtime environment, AnyRef corresponds to java.lang.Object.
  • Nothing is a subtype of all types, also called the bottom type. There is no value that has type Nothing.
  • Null is a subtype of all reference types (i.e. any subtype of AnyRef). It has a single value identified by the keyword literal null.
  • Nil is empty collection. So Nil != null
  • Implicit conversion is very useful for Java <-> Scala type conversion
  • Generic method and generic class can infer type from value

Classes, tuples and traits

  • Traits == Java interface
  • Function can return tuple. Tuple type is TupleN (N can be 1-22)
  • You can compose a class use class D extends B with C, where B is concrete class, and C is a trait. In this case, C is usually an optional trait you can plugin.
  • Case class is a very good way to do immutable class
  • Traits and classes can be marked sealed which means all subtypes must be declared in the same file.
  • An object with the same name as a class is called a companion object. Conversely, the class is the object’s companion class.

Function and methods

  • Function & method can both be directly passed into another function as lambda. Just like go.
  • Function can also be return type as lambda
  • You can define private helper function inside a function
  • There can be implicit parameters. If you don't give it,
    • Scala will first look for implicit definitions and implicit parameters that can be accessed directly (without a prefix) at the point the method with the implicit parameter block is called.
    • Then it looks for members marked implicit in all the companion objects associated with the implicit candidate type.
  • Method can have generics too.
  • Just like Ruby, operators (like + - * /) are also methods. So you can define them for your type.
  • Use by-name-parameters to force re-evaluate method parameter

Object

  • Can do singleton class
  • Can do static class method
  • Can do factory method
  • .apply construct an object; .unapply extract an object
  • The return type of an unapply should be chosen as follows:
    • If it is just a test, return a Boolean. For instance case even().
    • If it returns a single sub-value of type T, return an Option[T].
    • If you want to return several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)].


  • If you want to share an object across package, use package object

Classes relationship

  • This article explains how scala can delete variance fault in compile time: Scala与Java之间的型变对比 - 映柳枫鹏的文章 - 知乎 and scala 逆变有什么用? - 夏梓耀的回答 - 知乎.
  • Variances is used for defining relationship between super-types. If A < B, then it defines the relations of List[A] and List[B].
  • Upper type bound makes generic super class possible, e.g. List[T].
  • Lower type bound makes generic child class possible. It usually works with covariant type.
  • Suppose class B has an inner class A, class C also has an inner class A. In Java, B.A == C.A. In scala, B.A == C.A.
  • trait and abstract class can have generic too.
  • In Java, A implements B, C. In scala, A with B with C (Since you can't do A extends B, C).
  • Trait can access other trait's member using self type

Annotation

  • Like Java, scala accept annotation
  • Scala can optimize for tail-recursion. Just add @tailrec. (About tail recursion, see 什么是尾递归? - 知乎)
  • Just like C++, you can make method inline. Just add @inline
  • All Java annotations can be used in Scala
编辑于 04-26

文章被以下专栏收录