马丁 Odersky访谈录所思


Hierarchy

基本的持续结构如下图,图片源于官网:

语言 1

hierarchy.png

Object只是AnyRef的2个子类。

对于第3点,作者个人的知晓是在采取Scala天性的时候,要留意控制,不要去戏弄Scala语法中那么些奇技淫巧,从而让代码变得别扭难懂。Facebook的壹对工程师之所以对scala抱有微词,多数嘲谑点就是在代码的可读性与维护性方面。第3点同样是为着化解此题材。推文(Tweet)的文档Effective
Scala
用例子阐释了为中等步骤命名的重中之重。如下例子:

因此能够计算出继续的性状:

  1. 继承class A extends B with C with D实在能够作为是class A extends << class B with C with D >>,切对于那个父类class B with C with D:
    1. 多重继承的要害字with只利用于trait
    2. 当出现多重继承,后边trait语言,注脚的品质和方法会覆盖后边的类/trait
      谈到底组成的新class,作为class A父类
  2. 父类接轨的属性会作为子类独立的质量被引用,且子类不可能透过super引用父类的属性。
  3. 对此艺术的接二连三,1切同java一致

马丁 Odersky言必有中地交给了八个编写Scala代码的基准:

Generic

相比较简单的事例

class Valuable(value:Int) {

    def getValue(): Int = value
}

object Comparable {

    def add[E <: { def getValue():Int }](l: E, r: E):Int = l.getValue() + r.getValue()

    def main(args: Array[String]): Unit = {
        println(add(new Valuable(1), new Valuable(2)))
    }
}

// output:
// 3

Martin Odersky

Trait

trait类似于java中的接口,但辅助部分的落到实处。贰个trait实质上是被编写翻译成了相应的接口和抽象类七个文件。

trait Singer {

    val song:String = "sunshine"

    def singe(): Unit = {
        println("singing " + song)
    }

    def name(): String
}

class Star extends Singer {

    override def name(): String = {
        return "Lilei"
    }
}

object Star {

    def main(args: Array[String]): Unit = {
        val s = new Star()
        s.singe()
        println(s.name())
    }
}

// output:
// singing sunshine
// Lilei

编写翻译出的class文件:

public abstract interface Singer {

    public abstract void com$study$classdef$witht$Singer$_setter_$song_$eq(String paramString);

    public abstract String song();

    public abstract void singe();

    public abstract String name();
}

public abstract class Singer$class {

    public static void $init$(Singer $this) {
        $this.com$study$classdef$witht$Singer$_setter_$song_$eq("sunshine");
    }

    public static void singe(Singer $this) {
        Predef..MODULE$.println(new StringBuilder().append("singing").append($this.song()).toString());
    }
}

public class Star implements Singer {
    private final String song;

    public String song() {
        return this.song;
    }

    public void com$study$classdef$witht$Singer$_setter_$song_$eq(String x$1) {
        this.song = x$1;
    }

    public void singe() {
        Singer$class.singe(this);
    }

    public Star() {
        Singer$class.$init$(this);
    }

    public String name() {
        return "Lilei";
    }
}

能够看看:

  • 在Star的构造函数中会调用trait的抽象类的static
    $init$()措施来开始化从trait继承来的song属性。继承来的song属性被发明到了Star中,与scala中的trait已经退出了关联。在java中,interface里注脚的都以static变量,所以scala那样达成也是很客观的。

即便采纳方式匹配(帕特tern
Match)确实是很好的Scala实践,但就以此事例而言,其实Monadic
Chaining的不2诀要可以用for comprehension来改写。极度简单,可读性极佳:

为方便阅读,以下展示的编译后的class文件有做适合调整,且多数的main函数的object编译内容并未有出示

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

Extends class

语法基本和java一样,有多少个前提须要小心的:

  1. var品种无法被子类重写
  2. 从父类继承的变量通过向来引用的点子使用
  3. 不能够动用super去调用父类的变量,var和val类型的都无法。
  4. 因为上好几,所以随后对此变量的具有再次来到值,都以重写后的值,哪怕是调用的父类方法

scala中定义的继续关系

class Singer(name: String) {

    val prefix:String = "singer: "
    var gender:String = " male"

    def info(): String = {
        prefix + name
    }
}

class Star(name: String) extends Singer(name) {

    override val prefix: String = "star: "

    override def info(): String = {
        prefix + super.info() + gender
    }
}

object Main {

    def main(args: Array[String]): Unit = {
        val star = new Star("Lilei")
        println(star.info())
    }
}

// output:
// star: star: Lilei male

那样的输出其实跟java语言的企盼是差别等的。接着在编写翻译出的class文件里找原因:

public class Singer {

    private final String name;

    public String prefix() {
        return this.prefix;
    }

    private final String prefix = "singer: ";

    public String gender() {
        return this.gender;
    }

    public void gender_$eq(String x$1) {
        this.gender = x$1;
    }

    private String gender = " male";

    public String info() {
        return new StringBuilder().append(prefix()).append(this.name).toString();
    }

    public Singer(String name) {
        this.name = name;
    }
}

public class Star extends Singer {

    public Star(String name) {
        super(name);
    }

    public String prefix() {
        return this.prefix;
    }

    private final String prefix = "star: ";

    public String info() {
        return new StringBuilder().append(prefix()).append(super.info()).append(gender()).toString();
    }
}

能够看到:

  1. 父类的脾气是透过调用super()拓展伊始话的,那点和java一样
  2. 父类的习性都以private修饰符,对外暴光public的getter。假使子类重写父类的个性,那么相应的getter正是子类重写的办法,且只会再次来到子类的属性值,所以父类的就甩掉了,即子类继承的习性与父类脱离关系

Odersky在访谈中聊到了有个别对前途Scala的规划,包罗Tasty与Dotty,前者是为了消除Scala2进制不包容难题,Dotty则是为Scala提供新的编写翻译器。但是,Odersky的答疑令人痛心,2者的的确生产还索要静观其变几年时光。

Currying

柯里化。不废话了,看例子。

object Main {

    def currying(left: Int)(middle: Int)(right: Int): Int = left + middle + right

    def main(args: Array[String]): Unit = {
        val two = currying(2)(_)
        println(two(3)(4))

        val towPlusThree = two(3)
        println(towPlusThree(4))
    }
}

// output:
// 9
// 9

函数能够依次传入八个参数,切每传入叁个参数就能生成八个可反复选择的新函数。

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

class defination

整个class的布局内都是构造函数,构造函数的参数间接跟在表明背后。在new对象的时候,里面包车型地铁有着代码都会履行。

class Person(name: String) {

    println("person info: " + info())    // 这里可以先引用info

    val age: Int = 18
    var hobby: String = "swimming"

    println("person info: " + info())    // 这里申明变量后引用

    def info(): String = {
        "name: %s; age: %d; hobby: %s".format(name, age, hobby)
    }
}

object Person {

    def main(args: Array[String]): Unit = {
        new Person("Lilei")
    }
}

// output:
// person info: name: Lilei; age: 0; hobby: null     <--- 变量都没被初始化
// person info: name: Lilei; age: 18; hobby: swimming

翻看编写翻译后的class文件能够驾驭差不多原因:

public class Person {

    private final String name;
    private final int age;
    private String hobby;

    public int age() {
        return this.age;
    }

    public String hobby() {
        return this.hobby;
    }

    public void hobby_$eq(String x$1) {
        this.hobby = x$1;
    }

    public Person(String name) {
        this.name = name;

        Predef$.MODULE$.println(new StringBuilder().append("person info: ").append(info()).toString());

        this.age = 18;
        this.hobby = "";

        Predef..MODULE$.println(new StringBuilder().append("person info: ").append(info()).toString());
    }

    public String info() {
        return new StringOps(Predef$.MODULE$.augmentString("name: %s; age: %d; hobby: %s")).format(Predef$.MODULE$.genericWrapArray(new Object[] { this.name, BoxesRunTime.boxToInteger(age()), hobby() }));
    }

    public static void main(String[] paramArrayOfString) {
        Person$.MODULE$.main(paramArrayOfString);
    }
}

public final class Person$
{
    public static final Person$ MODULE$;

    static {
        new Person$();
    }

    public void main(String[] args) {
        new Person("Lilei");
    }

    private Person$() {
        MODULE$ = this;
    }
}

看得出,在类的概念中,除了def,别的语句都是各类执行的
而且object表明的变量和办法都以在3个object_name+$取名的类中以static格局定义出来的

题图:来自ThoughtWorks洞见作品《SCALA之父MAEnclaveTIN
ODE奥迪Q3SKY访谈录》中的马丁 Odersky。

scala的法定文书档案写得太烂了。无法,只好找点资料,或许看scala编写翻译的class文件,反正最后都以java的语法,然后本身总计一下。

斯Parker的API设计是和Scala
集合类设计是均等的函数式风格,里面具体的实现为了追求质量用了命令式,你能够看看Scala集合里面包车型客车兑现函数为了质量也用了不少var。

Mixin Class Composition

scala中除了正规的三番四次,还可以经过with要害字组合trait类型。

class Person(name: String, age: Int) {

    println("person info: " + info())

    def info(): String = {
        "name: %s; age: %d".format(name, age)
    }
}

trait Child {

    println("child info: " + info())

    def info(): String = {
        " I am a child"
    }

    def play(): Unit
}

trait Chinese {

    println("chinese info: " + info())

    def info(): String = {
        "I am a Chinese, %s, %s".format(province(), gender)
    }

    def province(): String = {
        "Hunan"
    }

    val gender: String  = "male"
}

class Student(name: String, age: Int, grade: Int) extends Person(name, age) with Child with Chinese {

    println("student info: " + info())

    override def info(): String = {
        super.info() + " grade: %d".format(grade)
    }

    override def play(): Unit = {}
}

object Student {

    def main(args: Array[String]): Unit = {
        new Student("Lilei", 18, 1)
    }
}

// output:
// person info: I am a Chinese, Hunan, null grade: 1
// child info: I am a Chinese, Hunan, null grade: 1
// chinese info: I am a Chinese, Hunan, null grade: 1
// student info: I am a Chinese, Hunan, male grade: 1

编写翻译的class文件是那样的:

public abstract interface Child {

    public abstract String info();

    public abstract void play();
}

public abstract class Child$class {

    public static void $init$(Child $this) {
        Predef$.MODULE$.println(new StringBuilder().append("child info: ").append($this.info()).toString());
    }

    public static String info(Child $this) {
        return " I am a child";
    }
}

public abstract interface Chinese {

    public abstract void com$study$classdef$Chinese$_setter_$gender_$eq(String paramString);

    public abstract String info();

    public abstract String province();

    public abstract String gender();
}

public abstract class Chinese$class
{
    public static String info(Chinese $this) {
        return new StringOps(Predef..MODULE$.augmentString("I am a Chinese, %s, %s")).format(Predef..MODULE$.genericWrapArray(new Object[] { $this.province(), $this.gender() }));
    }

    public static String province(Chinese $this) {
        return "Hunan";
    }

    public static void $init$(Chinese $this) {
        Predef$.MODULE$.println(new StringBuilder().append("chinese info: ").append($this.info()).toString());

        $this.com$study$classdef$Chinese$_setter_$gender_$eq("male");
    }
}

public class Person {

    private final String name;

    public Person(String name, int age) {
        Predef$.MODULE$.println(new StringBuilder().append("person info: ").append(info()).toString());
    }

    public String info() {
        return new StringOps(Predef$.MODULE$.augmentString("name: %s; age: %d")).format(Predef..MODULE$.genericWrapArray(new Object[] { this.name, BoxesRunTime.boxToInteger(this.age) }));
    }
}

public class Student extends Person implements Child, Chinese {

    private final String gender;

    public String gender() {
        return this.gender;
    }

    public void com$study$classdef$Chinese$_setter_$gender_$eq(String x$1) {
        this.gender = x$1;
    }

    public String province() {
        return Chinese.class.province(this);
    }

    public Student(String name, int age, int grade) {
        super(name, age);
        Child$class.$init$(this);
        Chinese$class.$init$(this);

        Predef$.MODULE$.println(new StringBuilder().append("student info: ").append(info()).toString());
    }

    public String info() {
        return new StringBuilder().append(Chinese.class.info(this)).append(new StringOps(Predef..MODULE$.augmentString(" grade: %d")).format(Predef..MODULE$.genericWrapArray(new Object[] { BoxesRunTime.boxToInteger(this.grade) }))).toString();
    }

    public static void main(String[] paramArrayOfString) {
        Student..MODULE$.main(paramArrayOfString);
    }

    public void play() {}
}

能够看出,那只是后边extends和trait二种意况的复合版。在子类student的构造函数中,会挨个执行super()withtrait的$init$()办法。结合后边trait的兑现,能够知道肯定是后2个父类/trait的变量的值会作为新值,赋值给子类的同名变量。
并且从info()的兑现能够见见super引用被翻译成了Chinese,也正是后3个父类/trait定义的方法也会覆盖前三个。

不过Scala又无法离开JVM,不然Scala与Java包容带来的惠及就流失了。庞大的Java社区间接是Scala能够得出的财富呢。Scala会否成也JVM,败也JVM呢?

Duck Typing

要是会鸭子叫,就视为鸭子。

class Person {

    def singe(): Unit = {
        println("singing")
    }
}

object Main {

    def singe(singer: {def singe():Unit}): Unit = {  // 只要实现的singe方法就可以作为参数
        singer.singe()
    }

    def main(args: Array[String]): Unit = {
        val person = new Person()
        singe(person)
    }
}

// output:
// singing

此地供给专注的是:因为得是鸭子叫,所以措施名也不可能不1律。

假设大家阅读Databricks给出的编码规范,会发现Databricks为了质量思量,更赞成于采纳命令式形式去行使Scala,例如,规范提出选择while循环,而非for循环大概别的函数转换(map、foreach)。

OO: Class & Object

那么,那样的规范是不是是好的Scala实践吧?Odersky用“保守”壹词来评论那1规范,不知其本意怎样?

几年时间啊!再过几年,Scala会否成为前几日黄花呢?至少Java的前进趋势已经起来威逼Scala了。而JVM的多变是还是不是又会进一步为Scala的朝3暮四造成障碍呢?假诺还要思量版本包容难题,Scala的前途版本碰着堪忧啊。想想笔者都为Odersky感到厌烦啊。

// 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
  }
}

而是就笔者个人的习惯,更赞成于前者(使用zipWithIndex结合map),它应用更为从简的函数式风格。鱼与熊掌,不可兼得!那是二个题材!规范从可读性角度思考,不建议采纳Monadic
Chaining。例如,上边包车型地铁代码应用一而再五个flatMap:

lihaoyi的文章Strategic Scala Style: Principle of Least
Power
不是三个正经,而是一份Scala最棒实践。内容蕴含对不变性与可变性、接口设计、数据类型、卓殊处理、异步、重视注入的解析与提出。值得壹读。

坦白说,那个访谈未有提供太多Scala的养分(不知是还是不是翻译的标题),总以为Odersky在直面壹些有关语言的深刻难题时,显得闪烁其词。尽管Odersky搬出了沃尔玛(沃尔玛(Walmart))美利坚同车笠之盟、高盛、摩尔根斯坦利来压阵,却反给作者底气不足的感觉到。Scala倒霉的部分仍然太多了,它会妨碍大家对Scala做出正确地认清。Scala待化解的标题照旧太多了,lightbend任重(英文名:rèn zhòng)而道远。归根到底,从一开始,Odersky没有对Scala性格做出具有控制力的筹划,贫乏收敛,导致不可胜道feature叶影参差,败坏了Scala的声名。

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
}

标准建议,改写为更拥有可读性的秘籍:

可是,3个啼笑皆非的现状是,斯Parker的过多源代码并从未依照Scala推崇的特级实践。Odersky对此的解说是:

那样的代码即使不难,却不能够依心像意地反映小编的企图。就算正好地予以中间步骤命名,意义就尤其明亮了。

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
  • 尽心尽力用力量弱的作用;
  • 给中间步骤命名。

Scala属于语言中的“骑墙派”,只要你丰裕高明,就可知在OO与FP中跳转如意,怡然自得,为虎傅翼。所谓“骑墙”,反倒成了富有超强适应能力的“八面见光”,何乐不为?

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
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
  }
}

那只怕是Scala选拔多范式的根本原因呢。即使Scala借鉴了累累函数式语言的风味,例如Scheme和Haskell,但Scala并未有强制大家在编写代码时严格依据FP的尺度。大家须要在OO与FP之间画一条线。在代码的底细层面,Scala供给我们大力编写未有副成效(引用透明),提供组合子抽象的函数式风格代码;不过在一些景色下,又允许大家让位于OO的主持行政事务。

ThoughtWorks的TW洞见在七月宣告了对Scala之父Martin
Odersky的访谈
。Odersky的答复显得提纲挈领,仔细分析,依旧能从中获得广大蕴涵的新闻(固然或然是负面的音讯)。提问的中坚重点是语言之争。Scala是1门极具吸重力的言语,如同天生具备一种气质,轻易能够吸粉,但招黑的能力也一点也不逊色。它犹如是从象牙塔里钻研出来的,但又在许多大型项目和产品中得到了推行。有人转向了他,又有人之后背弃了它。就算说Ruby的助力是Rails,那么拉动着Scala在社区中成长的,其实遍地可知Spark的阴影。

万幸有二个斯Parker,是Spark拯救了Scala。可惜,斯Parker的编码规范却不抱有Scala范儿。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图