Scala用一種簡(jiǎn)潔的高級(jí)語言將面向?qū)ο蠛秃瘮?shù)式編程結(jié)合在一起。Scala的靜態(tài)類型有助于避免復(fù)雜應(yīng)用程序中的錯(cuò)誤,其JVM和JavaScript運(yùn)行時(shí)使您可以輕松訪問龐大的庫生態(tài)系統(tǒng)來構(gòu)建高性能系統(tǒng)。
軟件功能:
無縫Java互操作
Scala運(yùn)行在JVM上,因此Java和Scala堆?梢宰杂苫旌希瑢(shí)現(xiàn)完全無縫的集成。
類型推斷
所以類型系統(tǒng)感覺不那么靜態(tài)。不要為類型系統(tǒng)工作。讓類型系統(tǒng)為您工作!
并發(fā)與分發(fā)
對(duì)集合使用數(shù)據(jù)并行操作,對(duì)并發(fā)和分發(fā)使用actors,或者對(duì)異步編程使用future。
特點(diǎn)
結(jié)合Java風(fēng)格接口的靈活性和類的強(qiáng)大功能。想想有原則的多重繼承。
模式匹配
想想類固醇的“開關(guān)”。與類層次結(jié)構(gòu)、序列等匹配。
高階函數(shù)
函數(shù)是一級(jí)對(duì)象。以保證類型安全的方式組合它們。把它們用在任何地方,傳遞給任何人。
使用方法:
使用Scala實(shí)現(xiàn)文件的拷貝
讀取行:要讀取文件的所有行,可以調(diào)用scala.io.Source對(duì)象的getLines方法:也可以對(duì)getLines應(yīng)用toArray或toBuffer方法。
將這些行放到數(shù)組或緩沖當(dāng)中,將文件內(nèi)容讀成一個(gè)字符串:val lines = source.mkString。
讀取字符:要從文件中讀取字符,可以直接把Source對(duì)象當(dāng)做迭代器:如果想查看某個(gè)字符,但是不處理掉的話,調(diào)用source對(duì)象的buffered方法。
讀取詞法單元或數(shù)字:通過split方法對(duì)轉(zhuǎn)化成行的文件內(nèi)容進(jìn)行劃分,通過toInt或toDouble方法把字符轉(zhuǎn)化成整數(shù)或浮點(diǎn)數(shù)。
寫入文本:Scala沒有內(nèi)建的對(duì)寫入文件的支持,要寫入文本文件,可以使用java.io.PrintWriter.
值得一提的是FileChannel在使用前,必須要打開。需要通過InputStream/OutputStream/RandomAccessFile獲取,BufferedReader/BufferedWriter獲取不到。
在SCALA 3中導(dǎo)入建議新功能:
隱式允許編譯器為您“編寫”程序的重要部分。例如,編譯器可以召喚JSON序列化器和反序列化器以獲取完整的類型層次結(jié)構(gòu)。
但是,使用隱式操作可能會(huì)很困難。值得慶幸的是,斯卡拉3編譯器極大地提高了在缺少implicits,使其更容易看到的情況下顯示的錯(cuò)誤信息的質(zhì)量,其中一個(gè)隱含參數(shù)不能由編譯器,并推斷如何來解決這個(gè)問題。
本文在具體的代碼示例中展示了這些實(shí)際的改進(jìn)。
動(dòng)機(jī)
在2018年Scala開發(fā)人員調(diào)查中,“隱式”一詞出現(xiàn)在“學(xué)習(xí)Scala時(shí),您面臨的最大挑戰(zhàn)是什么?”問題中。
我們還看到,在2019年開發(fā)人員調(diào)查中,有35%的受訪者表示,處理隱式隱式缺失是他們?nèi)粘9ぷ髁鞒讨械闹饕袋c(diǎn)。此外,他們表示,使用隱式函數(shù)時(shí),他們遇到的兩個(gè)最痛苦的問題是“查找已推斷出的參數(shù)”和“修復(fù)“隱式找不到”錯(cuò)誤”。最后但并非最不重要的一點(diǎn)是,受訪者最常提到的與隱性相關(guān)的其他痛苦點(diǎn)是“進(jìn)口”。
幾個(gè)月前,杰米·湯普森(Jamie Thompson)與社區(qū)進(jìn)行了討論,以更好地理解問題。我們發(fā)現(xiàn)“條件”隱式可能與大多數(shù)問題有關(guān)。條件隱式是隱式定義,它們本身具有隱式參數(shù)。例如,一個(gè)隱式Ordering[List[A]]實(shí)例需要一個(gè)隱式 Ordering[A]實(shí)例:
implicit def orderingList[A](implicit orderingA: Ordering[A]): Ordering[List[A]]
考慮一下,當(dāng)您調(diào)用需要隱式方法的方法時(shí)會(huì)發(fā)生什么 Ordering[List[Int]]。編譯器將搜索此類隱式定義,并發(fā)現(xiàn)orderingList如果存在type的隱式實(shí)例,則該隱式定義可能是一個(gè)不錯(cuò)的選擇Ordering[Int]。編譯器搜索這樣的隱式定義(它在Ordering伴隨對(duì)象中找到 ),并Ordering[List[Int]] 通過將Ordering[Int]實(shí)例提供給隱式定義來召喚初始隱式參數(shù)orderingList。在此示例中,我們僅涉及兩個(gè)隱式定義,但是在實(shí)踐中,條件式隱式定義可以形成更長(zhǎng)的鏈。
現(xiàn)在,讓我們看看如果鏈中某處發(fā)生故障,Scala 2中會(huì)發(fā)生什么。例如,當(dāng)我們調(diào)用需要隱式 Ordering[List[Foo]]但沒有隱式Ordering[Foo]實(shí)例的方法時(shí):
class FooList(List(new Foo)).sorted
Scala 2編譯器產(chǎn)生以下錯(cuò)誤:
No implicit Ordering defined for List[Foo].
錯(cuò)誤消息說找不到Ordering類型的隱式實(shí)例 List[Foo]。但是,此消息不是很準(zhǔn)確。失敗的實(shí)際原因是Orderingtype 沒有隱式 實(shí)例Foo。因此,編譯器無法調(diào)用Ordering 類型的隱式實(shí)例List[Foo]。
這是我們發(fā)現(xiàn)的第一個(gè)具體問題:錯(cuò)誤消息并不準(zhǔn)確知道哪里鏈?zhǔn)侨彪[。
我們確定了第二個(gè)問題是有關(guān)implicits問題往往由于缺少進(jìn)口,但要找到什么來進(jìn)口是很難的。
下一節(jié)將展示Scala 3如何通過提供更詳細(xì)的錯(cuò)誤消息和可行的反饋來解決這兩個(gè)問題。
顯示問題出在哪里
如果在隱式定義鏈中找不到隱式參數(shù),Scala 3編譯器現(xiàn)在會(huì)顯示它可以構(gòu)建的完整鏈,直到找不到參數(shù)為止。這是一個(gè)模仿上述Ordering[List[A]]問題的示例:
// `Order` type class definition, similar to the `Ordering` type class of// the standard librarytrait Order[A] { def compare(a1: A, a2: A): Int}object Order { // Provides an implicit instance of type `Order[List[A]]` under the condition // that there is an implicit instance of type `Order[A]` implicit def orderList[A](implicit orderA: Order[A]): Order[List[A]] = ???}// Sorts a `list` of elements of type `A` with their implicit `order` relationdef sort[A](list: List[A])(implicit order: Order[A]): List[A] = ???// A class `Foo`class Foo// Let’s try to sort a `List[List[Foo]]`sort(List(List(new Foo)))
Scala 3編譯器給出以下錯(cuò)誤消息:
Error:| sort(List(List(new Foo)))| ^|no implicit argument of type Order[List[Foo]] was found for parameter order of method sort.|I found:|| Order.orderList[A](/* missing */implicitly[Order[Foo]])||But no implicit values were found that match type Order[Foo].
錯(cuò)誤消息現(xiàn)在顯示了編譯器通過鏈接隱式定義而走了多遠(yuǎn),以及由于找不到隱式參數(shù)而最終停止在哪里。在我們的例子中,我們看到編譯器嘗試了定義,orderList但是沒有找到一個(gè)隱式Order[Foo]。因此,我們知道要解決此問題,我們需要實(shí)現(xiàn)一個(gè)隱式Order[Foo]。
記錄下來,顯示完整的隱式鏈的想法是Torsten Schmits在splain編譯器插件中提出的,該插件可在Scala 2中使用。
建議如何解決問題
如果缺少的隱式參數(shù)定義在某個(gè)地方但需要導(dǎo)入,則Scala 3編譯器會(huì)向您建議import可以解決該問題的子句。
這是說明此的示例:
// A class `Bar`class Bar// An implicit `Order[Bar]`// (note that it is _not_ in the `Bar` companion object)object Implicits { implicit def orderBar: Order[Bar] = ???}// Let’s try to sort a `List[Bar]`sort(List(new Bar))
編譯器產(chǎn)生以下錯(cuò)誤:
Error:| sort(List(new Bar))| ^|no implicit argument of type Order[Bar] was found for parameter order of method sort||The following import might fix the problem:|| import Implicits.orderBar
Scala 3編譯器不僅僅是報(bào)告未找到隱式參數(shù),還尋找可能提供缺少參數(shù)的隱式定義。在我們的情況下,編譯器建議使用import Implicits.orderBar,它確實(shí)可以修復(fù)編譯錯(cuò)誤。
一個(gè)更復(fù)雜的例子
一個(gè)典型的例子是貓traverse庫的操作。該操作被定義為 存在隱式實(shí)例的任何類型的條件擴(kuò)展方法。該操作采用一個(gè)函數(shù)和一個(gè)類型為隱式的參數(shù)。F[A]Traverse[F]A => G[B]Applicative[G]
實(shí)際上,這種非常通用的操作用于各種特定的上下文中。例如,將驗(yàn)證結(jié)果列表轉(zhuǎn)換為包含列表的單個(gè)驗(yàn)證結(jié)果,或?qū)⒖蛇x的異步結(jié)果轉(zhuǎn)換為異步的可選結(jié)果。但是,由于它是一種條件擴(kuò)展方法,并且由于它采用了隱式參數(shù),因此很難找到正確的導(dǎo)入使其起作用。
您無需熟悉類型類Traverse,也無需Applicative了解本文的其余部分。關(guān)于該操作,只有兩件事要了解traverse:
List[A]如果存在類型的隱式實(shí)例Traverse[List](它是一個(gè)條件擴(kuò)展方法),則它可用于類型的值,
操作本身采用類型為的隱式參數(shù)Applicative。
可以使用擴(kuò)展方法在Scala 3中對(duì)此進(jìn)行建模:
// The `Traverse` type class, which provides a `traverse` operation as an extension methodtrait Traverse[F[_]] { def [G[_], A, B](fa: F[A]).traverse(f: A => G[B])(implicit applicative: Applicative[G]): G[B]}// The applicative type class (its actual definition does not matter for the example)trait Applicative[F[_]]
假設(shè)在對(duì)象中定義了類型的給定實(shí)例和類型Traverse[List]的給定實(shí)例(給定的實(shí)例是在Scala 3中定義隱式實(shí)例的新方法):Applicative[Option]Givens
object Givens { given traverseList as Traverse[List] = ??? given applicativeOption as Applicative[Option] = ???}
現(xiàn)在我們已經(jīng)設(shè)置了上下文,讓我們來看一個(gè)使用的具體示例traverse。
首先,考慮一個(gè)函數(shù)parseUser,該函數(shù)User 從中解析a String(例如,包含JSON對(duì)象):
def parseUser(string: String): Option[User]
函數(shù)的返回類型為Option[User],可以表示的解析失敗None或的解析成功Some。
我們可以使用操作traverse和函數(shù)parseUser(解析一個(gè)用戶)來實(shí)現(xiàn)一個(gè)函數(shù)parseUsers,該函數(shù)解析一個(gè)用戶列表。該函數(shù)的簽名如下:
def parseUsers(strings: List[String]): Option[List[User]]
同樣,結(jié)果類型是Option[List[User]]可以表示解析失敗的結(jié)果(None如果任何字符串解析失敗,則返回)。
該功能可以實(shí)現(xiàn)如下:
def parseUsers(strings: List[String]): Option[List[User]] = strings.traverse(parseUser)
但是,如果嘗試使用Scala 2編譯此代碼,則會(huì)出現(xiàn)以下錯(cuò)誤:
value traverse is not a member of List[String]did you mean reverse?
該錯(cuò)誤消息無助于找到解決方案。
另一方面,使用Scala 3進(jìn)行編譯可以提供更好的幫助:
[E008] Not Found Error:| strings.traverse(parseUser)| ^^^^^^^^^^^^^^^^|value traverse is not a member of List[String], but could be made available as an extension method.||The following import might make progress towards fixing the problem:|| import Givens.traverseList
讓我們應(yīng)用建議并導(dǎo)入Givens.traverseList,F(xiàn)在,編譯器提供以下錯(cuò)誤:
Error:| strings.traverse(parseUser)| ^|no implicit argument of type Applicative[Option] was found for parameter applicative of method traverse in trait Traverse||The following import might fix the problem:|| import Givens.applicativeOption
如果我們應(yīng)用新建議(導(dǎo)入Givens.applicativeOption),我們的程序?qū)⒕幾g!
Scala 3編譯器首先建議使用import Givens.traverseList,以便擴(kuò)展方法traverse可用。然后,它建議使用import Givens.applicativeOption,這是調(diào)用該traverse 操作所必需的。
摘要
在Scala 2中處理“隱式找不到”錯(cuò)誤可能很困難,尤其是因?yàn)殚_發(fā)人員無法準(zhǔn)確看到在隱式定義鏈中找不到哪個(gè)隱式參數(shù),或者因?yàn)樗麄儾恢佬枰裁磳?dǎo)入添加到他們的程序中。
Scala 3通過以下方法解決了這兩個(gè)痛點(diǎn):
提供更精確的錯(cuò)誤消息,準(zhǔn)確顯示在隱式定義鏈中找不到哪個(gè)隱式參數(shù),
提供可行的反饋,建議import可能提供缺少的隱式內(nèi)容的條款。
您已經(jīng)可以在Dotty 0.24.0-RC1中嘗試此功能。
安裝方法:
下載Scala官方版的壓縮包,解壓后,雙擊msi文件,進(jìn)入安裝界面,點(diǎn)擊next
查看軟件協(xié)議,選擇i accept...,點(diǎn)擊next
設(shè)置軟件安裝位置,點(diǎn)擊browse可以自由設(shè)置,建議大家選擇安裝在D盤,然后點(diǎn)擊next
確認(rèn)安裝信息,點(diǎn)擊install
Scala官方版正在安裝,我們耐心等待
軟件安裝成功,點(diǎn)擊finish
接下來需要配置Scala的環(huán)境變量,需要提醒一下在安裝Scala之前需要安裝jdk,并且配置JDK的環(huán)境變量。我們看一下本地安裝完成后的目錄,如下圖所示。
最后我們配置Scala的環(huán)境變量,這臺(tái)電腦-->右鍵“屬性”-->高級(jí)系統(tǒng)設(shè)置-->環(huán)境變量,我們選擇Path環(huán)境變量,并點(diǎn)擊“編輯”按鈕,我們將上圖看到的Scala安裝目錄下的bean目錄配置到Path環(huán)境變量中即可。
安裝完成后我們需要檢驗(yàn)是否安裝成功,Win+R打開命令行,輸入 scala -version,若出現(xiàn)Scala的版本信息則說明安裝成功,如下圖所示。