大白話 Scala 控制抽象


2019-04-14

關鍵字: Scala、Scala控制抽象、Scala高階函數

本篇文章系筆者根據當前掌握的知識對 Scala 控制抽象的教材知識總結,不保證文章所述內容的絕對、完全正確性。


 

在 《快學Scala》 一書中,控制抽象被描述為是一系列語句的聚集,是一種特殊的函數,因為它是本質上只是對一系列語句的封裝,所以它理應:

1. 沒有參數輸入

2. 沒有值返回

教材中還給出了兩段代碼示例來解釋控制抽象。但這兩段代碼對於 Scala 初學者來說,可能沒那么好理解。這篇文章主要就針對教材中的示例作個白話解釋。

 

第 1 個例子

1 def runInThread(block: () => Unit) {
2     new Thread {
3         override def run() {
4             block()
5         }
6     }
7 }

這個例子的功能倒好理解,它就是一個將傳入的代碼塊放到一個全新的子線程里去執行的功能。這種場景在日常工作過程中是一很常見的場景,但是筆者認為這個例子對於初學者理解 控制抽象 卻不是一個合適的例子。因為,有很多初學者甚至還沒來得學習 Scala 中的對象實例化及匿名內部類的知識,雖然在 Scala 中它長的與 Java 非常像,但對於猜測的知識而言,心里始終會有個芥蒂。本來是只想學習控制抽象知識點的,突然穿插一個可能是匿名內部類,可能是線程的知識點進來,會加重讀者學習負擔,甚至打擊學習信心。因此我准備換一個示例代碼,這個示例代碼除了控制抽象的知識以外,其余的全是單純邏輯代碼,不會對讀者對控制抽象的學習造成額外的負擔。

 

新的示例代碼就定義一個類似 “購物” 的功能模塊。封裝一個 myShop 函數,這個函數開放給顧客調用,每次調用都表示一位顧客的購買行為。

1   def myShop(block: () => Unit) {
2     
3     println("Welcome in!")
4     block()
5     println("Thanks for coming!")
6     
7   }

 

好,接下來就開始我們的學習。

 

首先,上面我們有提到:控制抽象是一系列語句的聚集。因此,在示例代碼中,千萬不要覺得 def myShop 就是控制抽象。block 作為 “語句塊” 才應該是控制抽象

block 才是控制抽象

 

函數 myShop 的參數的變量名稱是 block ,它的名稱也已經很直白了,說明這里就應該傳 “一塊” 代碼進來。而它的參數則是一個匿名函數類型 () => Unit ,它沒有輸入參數,也沒有值返回。隨后便是函數體的聲明,這種函數名后面直接接大括號來寫函數體的方式在 Scala 中被稱為 “過程” ,簡單說就是聲明這個函數( myShop 函數 )是一個沒有返回值的函數。在給這個 myShop 函數傳參時,直接將你需要的語句塊一骨腦扔進去就好。

 

《快學Scala》 中調用 runInThread 函數的方式是

1 runInThread {
2     () =>
3         println("Hi")
4         Thread.sleep(10000)
5         println("Bye")
6 }

類似地,在我們這個 myShop 函數中,也可以有如下調用方式

1 myShop {
2     () =>
3         println("I want a pencil")
4         println("I want a book")
5         println("I want your wechat")
6 }

myShop 例子的執行結果如下

 

emm... 這種調用方式乍看上去感覺像是在寫一個函數體一樣,感覺怪怪的。但其實只要你了解了 Scala 中 圓括號 與 大括號 的作用以后,這段代碼你看起來就沒什么毛病的了。

 

在 Scala 中,調用函數時,一般既可以使用圓括號來傳參,也可以使用大括號來傳參。不信?

scala 傳參的方式

所以,上面不管是 runInThread 還是 myShop 的函數調用,都只是在傳參而已。圓括號與大括號的區別在於可以傳遞的代碼量而已。圓括號只能傳遞一條語句,而大括號可以傳遞多條語句。因此,對於我們的 myShop 的例子來說,假如某個人只想買一件商品,那么,完全可以使用以下的調用方式。同時,由於控制抽象函數不需要傳參,所以一個 空的參數列表括號 在這也挺多余的,所以,函數聲明和調用都可以簡化一下。

 1 def myShop(block: => Unit) {
 2 
 3     println("Welcome in!")
 4     block
 5     println("Thanks for coming!")
 6 
 7 }
 8 
 9 def main(args: Array[String]){
10     myShop( println("I wanna buy a condom") )
11 }

怎么樣,這樣看起來是不是順眼多了? 它的執行結果當然也是沒毛病的。

當然,即使是只想買一件商品,使用大括號來調用也是沒有問題的

myShop{ println("I wanna buy a pen") }

 

不管是 runInThread 函數還是我們的 myShop 函數,最重要的都是把傳進來的代碼塊執行一次。有的同學可能會問:我為什么不直接單獨封裝我那一系列代碼塊,而非得復雜化成控制抽象來執行呢? 關於這點,我覺得如果您了解設計模式中的代理模式的話,可能就不會問出這個問題來了。附上一篇筆者多年前寫的博文: 大白話設計模式之代理模式  

 

第 2 個例子

《快學 Scala 》 中第 2 個例子是一個應用到柯里化與遞歸調用的控制抽象示例。

1 def until (condition: => Boolean) (block: => Unit) {
2     if(!condition) {
3         block
4         until(condition)(block)
5     }
6 }

在有了上面第 1 個例子的理解以后,對於這個例子的函數定義,應該就沒什么問題的了。而關於這個函數的調用則是

1 var x = 10
2 until (x == 0) {
3     x -= 1
4     println(x)
5 }

 

相信這個函數調用,很多初學者都看的挺懵的,筆者也不例外。

 

這個例子書中說是模擬一個 while 表達式出來,看它的調用方式,也確實和 while 表達式一樣。但是我們的 until 函數明明是定義了兩個參數的,這里只傳一個真的合適嗎?

 

還是那句話:在理解了 Scala 中圓括號和大括號的作用以后,這段調用就顯得各種沒毛病了。這個調用中,確確實實是以柯里化的形式傳了兩個參數進去的。只不過第 1 個參數是以圓括號的形式來傳遞的,第 2 個參數由於是一系列的代碼塊,所以要用大括號的形式來傳遞。

花式柯里化調用

假如,我們在這個例子中的控制抽象函數只有一條語句的話,until 的調用形式完全可以改成下面這種更明朗的格式

1 var x = 10
2 until (x == 0)( x -= 1)

這是不是就很柯里化了?

 


The End


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM