Scala精粹

戲路如流水,從始至終,點(diǎn)滴不漏。一路百折千回,本性未變,終歸大海。一步一戲,一轉(zhuǎn)身一變臉,撲朔迷離。真心自然流露,舉手投足都是風(fēng)流戲。一旦天幕拉開,地上再無演員。

Scala是由Martin Ordersky設(shè)計(jì)的一門混合「面向?qū)ο蟆购汀负瘮?shù)式」,并具備完備的「泛型編程」能力,支持多種范式的程序設(shè)計(jì)語言。

Scala取名為「可擴(kuò)展的語言」,因?yàn)樗鼡碛辛己玫膹椥?。它使用不變的語言內(nèi)核,構(gòu)建萬千變化的世界。同時,Scala也是設(shè)計(jì)「DSL(領(lǐng)域描述語言)」的利器,讓編程充滿輕松,愉快的氣氛,并富有成就感。

Scala擁有強(qiáng)大的類型系統(tǒng),具有豐富的表達(dá)能力,語法簡潔,優(yōu)雅,靈活。它應(yīng)用廣泛,從編寫簡單的腳本,到構(gòu)建大型的企業(yè)級系統(tǒng)。

Scala運(yùn)行于JVM之上,并與現(xiàn)有的JVM生態(tài)系統(tǒng)無縫鏈接,而無需定義額外的膠水代碼。它兼容既有Java的類庫,讓成千上萬的程序可以繼續(xù)工作,并能夠得以復(fù)用。

Scala的哲學(xué)

Scala的設(shè)計(jì)吸收了眾多程序設(shè)計(jì)語言優(yōu)秀的思想,取精除粕,形成了自身特有的哲學(xué)思維體系。

  • 自由:釋放自由,方能創(chuàng)造奇跡;
  • 復(fù)用:討厭重復(fù),重用既有代碼;
  • 抽象:正交設(shè)計(jì),擁抱未來變化;
  • 開放:對外擴(kuò)展開放,對內(nèi)拒絕修改;
  • 友好:專家級的瑞士軍刀;

Scala的基因

Scala首先偏向Java社區(qū)的使用習(xí)慣,包括表達(dá)式,代碼塊,還有包和引用的等語法習(xí)性。而對于Java用戶唯一提出挑戰(zhàn)的就是「類型修飾」被放在變量后面了;但是,當(dāng)習(xí)慣了Scala代碼風(fēng)格后,你會發(fā)現(xiàn)「后置類型修飾」具有很多優(yōu)勢。

Scala借鑒了Smalltalk的「對象模型」,修正了Java對象模型存在的諸多不足。例如,在沒有損失性能的前提下,將AnyVal, AnyRef兩者完美統(tǒng)一起來;不僅考慮層次的頂端,還設(shè)計(jì)了層次的末端,例如,Nothing的抽象,對「類型推演」具有重大意義。

Scala也借鑒了Haskell「類型系統(tǒng)」的設(shè)計(jì),及其「函數(shù)式」的思維,并結(jié)合自身特性,優(yōu)雅地將OOFP整合在一起,取長補(bǔ)短,極大地增強(qiáng)了Scala的威力。

Scala也借鑒了C++「多重繼承」,并吸收了RubyMixin的特性,設(shè)計(jì)了強(qiáng)大的trait機(jī)制實(shí)現(xiàn)靈活的對象組合機(jī)制。

Scala也借鑒了Erlang的思想,在沒有改變內(nèi)核的情況下,通過擴(kuò)展類庫的方式支持actor的并發(fā)編程模式。

Scala也借鑒了C++語言的一些特性,例如「操作符重載」,「隱式轉(zhuǎn)換」等特性;尤其增強(qiáng)了的「隱式轉(zhuǎn)換」成為Scala可擴(kuò)展性的重要機(jī)制。

Scala的特質(zhì)

接下來,通過幾個簡單的例子,闡述Scala所具有的一些特點(diǎn),并闡述選擇Scala的動機(jī)。

Scala是自由的

No One Size Fits All.

Scala既增強(qiáng)了OO的語義,也引入了FP的思維,同時也擁有完備的「泛型編程」能力,它們互相截長補(bǔ)短,并融為一體。

Scala更像一把瑞士軍刀,支持多種編程范式。Scala程序員擁有豐富的工具箱,當(dāng)面對具體問題時擁有很大的自由空間,力求使用最簡單的方法解決問題。

需求:要定義一個「讀取器」,可以從字符中直接獲取,可以從文件中讀取,甚至從網(wǎng)絡(luò)IO讀取。

為了得到一致的抽象,可以定義Reader的抽象體,并對「數(shù)據(jù)源」這一變化方向進(jìn)行分離。Scala是一門多范式的程序設(shè)計(jì)語言,這里嘗試使用兩種不同的思維嘗試解決這個問題。

類型參數(shù)

先定義泛型的Reader[+T],并賦予協(xié)變的能力。

trait Reader[+T] {
  val source: T
  def read: String
}

然后,再子類化一個StringReader,數(shù)據(jù)源從字符串中直接獲取。

class StringReader(val source: String) extends Reader[String] {
  def read: String = source
}

再定義一個FileReader,數(shù)據(jù)源從文本文件中獲取。

class FileReader(val source: File) extends Reader[File] {
  def read: String = 
    using(Source.fromFile(source)) { file => 
      file.getLines.mkString(Properties.lineSeparator) 
    }
}

using是一個自定義的抽象控制結(jié)構(gòu),用于保證資源的安全釋放,它是「借貸模式」的經(jīng)典應(yīng)用,后文將對它進(jìn)行闡述。

抽象類型

也可以先定義一個抽象類型:type In;基于抽象類型In,再定義了一個抽象字段:val source: In;最后,Reader還定義了一個抽象方法read

trait Reader {
  type In
  val source: In
  def read: String
}

然后,再子類化一個StringReader,數(shù)據(jù)源從字符串中直接獲取。

class StringReader(val source: String) extends Reader {
  type In = String
  def read: String = source
}

如果數(shù)據(jù)源從文本文件中獲取,FileReader實(shí)現(xiàn)如下:

class FileReader(val source: File) extends Reader {
  type In = File
  def read: String = 
    using(Source.fromFile(source)) { file =>
      file.getLines.mkString(Properties.lineSeparator) 
    }
}

Scala是抽象的

Scala對于控制系統(tǒng)的復(fù)雜度擁有強(qiáng)大的抽象能力。甚至具備「控制結(jié)構(gòu)」的抽象能力,使得設(shè)計(jì)更加正交,合理,程序更加簡單,優(yōu)雅。

需求1:判斷某個單詞是否包含數(shù)字

使用Java

可以使用迭代快速實(shí)現(xiàn)這個需求。

public static boolean hasDigit(String word) {
  for (int i = 0; i < word.length(); i++)
    if (Character.isDigit(word.charAt(i)))
      return true;
    return false;
}

需求2:判斷某個單詞是否包含大寫字母

當(dāng)然,可以通過復(fù)制粘貼,重復(fù)實(shí)現(xiàn)相同的邏輯;但是將導(dǎo)致明顯的重復(fù)設(shè)計(jì)。

public static boolean hasUpperCase(String word) {
  for (int i = 0; i < word.length(); i++)
    if (Character.isUpperCase(word.charAt(i)))
      return true;
    return false;
}

為了得到更為抽象的設(shè)計(jì),使得代碼具有高度的可復(fù)用性,可以提取一個抽象的CharacterSpec概念。

public interface CharacterSpec {
  boolean satisfy(char c);
}

hasDigit, hasUpperCase合二為一,實(shí)現(xiàn)算法邏輯的代碼復(fù)用。

public static boolean exists(String word, CharacterSpec spec) {
  for (int i = 0; i < word.length(); i++)
    if (spec.satisfy(word.charAt(i)))
      return true;
    return false;
}

可以如下使用這個函數(shù),判斷單詞是否包含數(shù)字。

exists(word, new CharacterSpec() {
  @Override
  public boolean satisfy(char c) {
    return Character.isDigit(c);
  }
});

對于判斷是否包含大寫字母,則可以實(shí)現(xiàn)為:

exists(word, new CharacterSpec() {
  @Override
  public boolean satisfy(char c) {
    return Character.isUpperCase(c);
  }
});

但是,使用匿名內(nèi)部類,將導(dǎo)致復(fù)雜的程序結(jié)構(gòu)和冗余的語法噪聲。

使用Java8

可以使用Java8lambda表達(dá)式簡化設(shè)計(jì)。

exists(word, c -> Character.isDigit(c));

如果使用「方法引用」,可以進(jìn)一步改善程序的表達(dá)力。

exists(word, Character::isDigit);

但是,即使使用Java8,設(shè)計(jì)依然還是美中不足。其一,exists擁有兩個參數(shù),如果能夠做到如下的「代碼塊」,那就太神奇了。

// 假設(shè)可以定義代碼塊
exists(word) { Character::isDigit };

其二,如果將exists成為String的一個方法,設(shè)計(jì)將更加具有OO的風(fēng)格了。

// 假設(shè)String擁有exists方法
word.exists(Character::isDigit);

可惜的是,Java并沒有上述的能力。

使用Scala

首先,Scala可以兼容既有的Java設(shè)計(jì),而無需付出額外的成本。按照慣例,對Java實(shí)現(xiàn)的StringUtil.exists可以做一個簡單的包裝,隱藏匿名內(nèi)部類的實(shí)現(xiàn)細(xì)節(jié),并對外提供「代碼塊」定制的風(fēng)格。

def exists(s: String)(p: Char => Boolean): Boolean =
  return StringUtil.exists(s, new CharacterSpec {
    override def satisfy(c: Char): Boolean = p(c)
  })

也就是說,相比Java的實(shí)現(xiàn),Scala借助「柯里化」的機(jī)制,進(jìn)一步改善代碼的表達(dá)力。

exists(word) { _.isUpper }

事實(shí)上,Scala運(yùn)用「隱式轉(zhuǎn)換」的神奇魔法,可以將String的功能增強(qiáng)為StringOps,使其直接能夠調(diào)用exists方法。

word.exists(_.isUpper)

如果偏愛大括號,也可以寫成這樣:

word.exists { _.isUpper }

Scala是簡潔的

Scala極度討厭「重復(fù)」,嚴(yán)格堅(jiān)持DRY(Don't Repeat Youself)原則。不僅體現(xiàn)在語法上,還包括類庫的設(shè)計(jì)上。

需求:設(shè)計(jì)一個貨幣的值對象。

使用Java

Java實(shí)現(xiàn)「值對象」時,語法較為啰嗦,并具有相同的模式,很容易形成重復(fù)的「樣板代碼」。

例如使用private定義字段,并在構(gòu)造函數(shù)進(jìn)行初始化;然后定義字段的Getter接口;即使equals,hashCode等具有邏輯的方法時,也表現(xiàn)為固定的模式。

public class Currency {
  private final int amount;
  private final String designation;

  public Currency(int amount, String designation) {
    this.amount = amount;
    this.designation = designation;
  }

  public String getDesignation() {
    return designation;
  }

  public int getAmount() {
    return amount;
  }

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object obj) {
    ...
  }
}

Java社區(qū),也存在實(shí)用方法,或者類庫,支持equals, hashCode的自動生成,但也要讓程序員付出額外的努力。

使用Scala

Scala對于重復(fù)的事情從來不說兩次。對于固定的模式,擁有最直接、最簡潔的表達(dá)方式,從而大幅地削減了代碼量。

case class Currency(amount: Int, designation: String)

Scala社區(qū),case class是定義「值對象」的最佳實(shí)現(xiàn)模式。它天然地?fù)碛?code>Getter, equals, hashCode等方法實(shí)現(xiàn),并且在其「伴生對象」中擁有apply的工廠方法。

Scala是性感的

Scala語法輕量,并具備豐富的表達(dá)力。借助于Scala強(qiáng)大的「類型推演」能力,Scala的簡潔程度可以和「動態(tài)語言」相媲美。

需求:建立一個電話簿的數(shù)據(jù)表格。

使用Java

使用Java建立一個簡單的電話簿數(shù)據(jù)表格。

Map<String, String> phonebook = new HashMap<String, String>() {{
  put("Horance", "+9519728872");
  put("Dave", "+9599820012");
}};

其中,參數(shù)類型<String, String>重復(fù)地聲明了兩次,構(gòu)造靜態(tài)表也使用了特殊的「初始化」的語義。

使用Scala

使用Scala,代碼實(shí)現(xiàn)不僅輕量,表現(xiàn)力也相當(dāng)不錯。

val phonebook = Map(
  "Horance" -> "+9519728872"
  "Dave"    -> "+9599820012"
)

沒有冗余的類型噪聲,而且具有更形象的語法描述。更重要的是,->操作符并不是語言內(nèi)核所支持的,而是通過簡單地類庫擴(kuò)展而實(shí)現(xiàn)的。

也就是說,"Horance" -> "+9519728872"構(gòu)造了一個類型為Tuple2[String, String]的二元組,它等價(jià)于("Horance", "+9519728872")

事實(shí)上,->定義在Predef中。

object Predef {
  implicit final class ArrowAssoc[A](self: A) extends AnyVal {
    def ->[B](y: B) = (self, y)
  }
}

ArrowAssoc實(shí)現(xiàn)了self: A的功能增強(qiáng),使其擁有->方法,而該方法返回一個二元組。

Scala是多變的

Scala猶如變形金剛,擁有無窮變化的空間。Scala倡導(dǎo)一個問題,擁有多種解法的思維習(xí)慣。當(dāng)面對具體問題時,使得程序員擁有更多的自由選擇權(quán)。

需求:打印程序選項(xiàng)列表

使用var

Scala雖然倡導(dǎo)函數(shù)式的思維,但在某些性能苛刻的場景,指令式可能成為最后的救命稻草。

object Main extends App {
  var i = 0
  while (i < args.length) {
    println(args(i));
    i += 1
  }
}
for推導(dǎo)式
object Main extends App {
  for (arg <- args)
    println(arg)
}
函數(shù)字面值

Scala的函數(shù)具有一等公民的地位,跟其他值一樣可以被傳遞和存儲。foreach就是一個高階函數(shù),它接受函數(shù)字面值作為參數(shù)進(jìn)行傳遞。

object Main extends App {
  args.foreach((arg: String) => println(arg))
}
類型自動推演
object Main extends App {
  args.foreach(arg => println(arg))
}
占位符

因?yàn)?code>arg在foreach中僅出現(xiàn)過一次,可以使用_的占位符簡化實(shí)現(xiàn)。

object Main extends App {
  args.foreach(println(_))
}
部分應(yīng)用函數(shù)

也可以將println看成一個整體進(jìn)行傳遞。需要注意的是,println_之間有且僅有一個空格。

object Main extends App {
  args.foreach(println _)
}

對于此例,可以將傳遞println函數(shù)名,簡化實(shí)現(xiàn),提高表現(xiàn)力。

object Main extends App {
  args.foreach(println)
}
可選的逗號和括號

在特殊場景下,逗號和括號是可選的,代碼的表現(xiàn)力猶如自然語言一樣直白。

object Main extends App {
  args foreach println
}

Scala是開放的

Open for Extension.

Scala具有高度可擴(kuò)展性的架構(gòu),借助于trait,抽象類型和泛型,Self Type,隱式轉(zhuǎn)換等機(jī)制,使得它具備強(qiáng)大的靈活性。

using的設(shè)計(jì)為例,講解Scala對于擴(kuò)展性的支持,及其設(shè)計(jì)內(nèi)部DSL的技術(shù),加深對Scala的理解。

形式化

對于資源管理,可以簡化為如下的數(shù)學(xué)模型:

Input: Given resource: R
Output:T
Algorithm:Call back to user namespace: f: R => T, and make sure resource be closed on done.

它表示:給定一個資源R,并將資源R傳遞給用戶空間,并回調(diào)算法f: R => T;當(dāng)過程結(jié)束時資源自動釋放。

using實(shí)現(xiàn)
import scala.language.reflectiveCalls

object using {
  def apply[R <: { def close(): Unit }, T](resource: => R)(f: R => T): T = {
    var source: Option[R] = None
    try {
      source = Some(resource)
      f(source.get)
    } finally {
      for (s <- source)
        s.close
    }
  }
}

using常常被稱為「借貸模式」,是保證資源自動回收的重要機(jī)制。

控制抽象

使得using形如內(nèi)置于語言的控制結(jié)構(gòu),其行為類似于if, while一樣。

def read: String = using(Source.fromFile(source)) { file =>
  file.getLines.mkString(Properties.lineSeparator) 
}
鴨子編程

R <: { def close(): Unit }用于約束R類型的特征,它必須擁有def close(): Unit方法。這是Scala支持「鴨子編程」的一個重要技術(shù)。

例如,File滿足R類型的特征,因?yàn)樗鼡碛?code>close方法。

惰性求值

resource: => R是按照by-name傳遞,在實(shí)參傳遞形參過程中,并未對實(shí)參進(jìn)行立即求值,而將求值推延至resource: => R的調(diào)用點(diǎn)。

例如,using(Source.fromFile(source))并沒有馬上調(diào)用fromFile方法,并傳遞給形參,而將求值推延至source = Some(resource)語句,即調(diào)用Some.apply方法時才展開計(jì)算。

Option

Scala社區(qū),Option常常用于表示「存在與不存在」兩者之間的語義。它存在兩種形式:Some, None。

for推導(dǎo)式

finally關(guān)閉資源時,使用for推導(dǎo)式過濾掉None。也就是說,如下三種形式是等價(jià)的。

  • 過濾掉None,并自動提取Option中的元素。
for (s <- source)
  s.close
  • 使用if,但需要從Some中手動get。
if (source != None)
  source.get.close

Scala的明天

軟件設(shè)計(jì)的目的就是為了控制復(fù)雜性,讓軟件應(yīng)對未來變化具有更好的彈性。Scala強(qiáng)大而自由,當(dāng)程序員設(shè)計(jì)一個應(yīng)用和類庫時,具有很大的自由空間。

但是,Scala過度的靈活性,往往會誘惑他人掉進(jìn)復(fù)雜性的深淵而不能自拔。它猶如具有「魔戒」的力量,雖然強(qiáng)大,但也很致命。

Complexity is like a bug light for smart people. We can't resist it, even though we know it's bad for us. -- Alex Payne.

因此,應(yīng)該理智地抵制復(fù)雜性的誘惑,才能真正地發(fā)揮Scala的威力。使用Scala不是為了炫技,而應(yīng)該盡最大的可能讓設(shè)計(jì)保持簡單。

Martin Ordersky也在2016年元旦之初發(fā)文,號召社區(qū)有志之士在未來的時間里盡最大可能地降低Scala的復(fù)雜度。

我堅(jiān)信,Scala的明天會更簡單,更加漂亮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容