Scala參數(shù)傳遞

在抉擇的哪一刻,成敗實已露出端倪巾表。

Scala擁有兩種參數(shù)傳遞的方式:Call-by-Value(按值傳遞)與Call-by-Name(按名傳遞)汁掠。Call-by-Value避免了參數(shù)的重復求值,效率相對較高集币;而Call-by-Name避免了在函數(shù)調(diào)用時刻的參數(shù)求值考阱,而將求值推延至實際調(diào)用點,但有可能造成重復的表達式求值鞠苟。

兩者存在微妙的差異乞榨,并應用于不同的場景。本文將闡述兩者之間的差異偶妖,并重點討論Call-by-Name的實現(xiàn)模式和應用場景姜凄。

  1. 基本概念
  • val與值
  • def與方法
  • val與var
  • val與def
  1. 參數(shù)傳遞
  • 按值傳遞
  • 按名傳遞
  1. 借貸模式

基本概念

val與值

val用于「變量聲明」與「值(Value)」定義政溃。例如趾访,pi定義了一個常量,它直接持有Double類型的字面值董虱。

val pi = 3.1415926

val也可以直接定義「函數(shù)值(Function Literals)」扼鞋。例如,max變量定義了一個類型為(Int, Int) => Int的函數(shù)值愤诱。

val max = (x: Int, y: Int) => Int = if (x > y) x else y

當使用val定義變量時云头,其引用的對象將被立即求值。max在定義時淫半,它立即對=的右側表達式進行求值溃槐,它直接持有(Int, Int) => Int類型的函數(shù)值。上例等價于:

val max = new Function2[Int, Int, Int] {
  def apply(x: Int, y: Int): Int = if (x > y) x else y
}

但是科吭,apply方法并沒有立即被求值昏滴。直至發(fā)生函數(shù)調(diào)用時才會對apply進行求值。

def與方法

def用于定義「方法(Method)」对人。例如谣殊,max定義了一個(Int, Int)Int的方法,它表示max是一個參數(shù)類型為(Int, Int)牺弄,返回值類型為Int的方法定義姻几。

def max(x: Int, y: Int): Int = if (x > y) x else y

當使用def定義方法時,其方法體并沒有立即被求值势告。但是蛇捌,每當調(diào)用一次max,方法體將被重復地被求值咱台。

返回函數(shù)

可以將上例max方法進行變換络拌,使其返回(Int, Int) => Int的函數(shù)值。

def max = (x: Int, y: Int) => if (x > y) x else y 

此時吵护,max定義了一個方法盒音,但省略了參數(shù)列表表鳍,其返回值類型為(Int, Int) => Int。它等價于

def max() = (x: Int, y: Int) => if (x > y) x else y 

因為max是一個「無副作用」的方法祥诽,按照慣例譬圣,可以略去「空參數(shù)列表」,即省略max后面的小括號()雄坪。一則對外聲明無副作用的語義厘熟,二則使代碼更加簡明扼要。

方法與函數(shù)

def max(x: Int, y: Int): Int = if (x > y) x else y
def max = (x: Int, y: Int) => if (x > y) x else y 

兩者都定義為「方法(Method)」维哈,但后者返回了一個函數(shù)(Function)類型绳姨。因此,后者常常也被習慣地稱為「函數(shù)(Function)」阔挠。

首先飘庄,它們兩者可以具有相同的調(diào)用形式:max(1, 2)。但對于后者购撼,調(diào)用過程實際上包括了兩個子過程跪削。

  1. 首先調(diào)用max返回(Int, Int) => Int的實例;
  2. 然后再在該函數(shù)的實例上調(diào)用apply方法迂求,它等價于:
max.apply(1, 2)

其次碾盐,兩者獲取函數(shù)值的方式不同。后者可以直接獲取到函數(shù)值揩局,而對于前者需要執(zhí)行η擴展才能取得等價的部分應用函數(shù)毫玖。

val f = max _

此時,f也轉(zhuǎn)變?yōu)?code>(Int, Int) => Int的函數(shù)類型了凌盯。實施上付枫,對于上例,η擴展的過程類似于如下試下十气。

val f = new (Int, Int) => Int {
  def apply(x: Int, y: Int): Int = max(x, y)
}

val與var

varval都可以用于定義變量励背,但兩者表示不同的語義。val一旦引用了對象砸西,便不能再次引用其它對象了叶眉。

val s1 = "Alice"
s1 = "Bob"   // Error

var引用變量可以隨時改變?nèi)ヒ闷渌膶ο蟆?/p>

var s2 = "Alice"
s2 = "Bob"  // OK

另外,var/val都可以引用不可變(Immutable)類的實例芹枷,也可以引用可變(Mutable)類的實例衅疙。

val s1 = new StringBuilder  // val可以引用可變類的實例
var s2 = "Alice"            // var也可以引用不可變類的實例

var/val的差異在于引用變量本身的可變性,前者表示引用隨時可修改鸳慈,而后者表示引用不可修改饱溢,與它們所引用的對象是否可變無關。

val與def

def用于定義方法走芋,val定義值绩郎。對于「返回函數(shù)值的方法」與「直接使用val定義的函數(shù)值」之間存在微妙的差異潘鲫,即使它們都定義了相同的邏輯。例如:

val max = (x: Int, y: Int) => if (x > y) x else y 
def max = (x: Int, y: Int) => if (x > y) x else y 

語義差異

雖然兩者之間僅存在一字之差肋杖,但卻存在本質(zhì)的差異溉仑。

  1. def用于定義「方法」,而val用于定義「值」状植。
  2. def定義的方法時浊竟,方法體并未被立即求值;而val在定義時津畸,其引用的對象就被立即求值了振定。
  3. def定義的方法,每次調(diào)用方法體就被求值一次肉拓;而val僅在定義變量時僅求值一次后频。

例如,每次使用val定義的max帝簇,都是使用同一個函數(shù)值徘郭;也就是說靠益,如下語句為真丧肴。

max eq max   // true

而每次使用def定義的max,都將返回不同的函數(shù)值胧后;也就是說芋浮,如下語句為假。

max eq max   // false

其中壳快,eq通過比較對象id實現(xiàn)比較對象間的同一性的纸巷。

類型參數(shù)

val代表了一種餓漢求值的思維,而def代表了一種惰性求值的思維眶痰。但是瘤旨,def具有更好可擴展性,因為它可以支持類型參數(shù)竖伯。

def max[T : Ordering](x: T, y: T): T = Ordering[T].max(x, y)

lazy惰性

def在定義方法時并不會產(chǎn)生實例存哲,但在每次方法調(diào)用時生成不同的實例;而val在定義變量時便生成實例七婴,以后每次使用val定義的變量時祟偷,都將得到同一個實例。

lazy的語義介于defval之間打厘。首先修肠,lazy valval語義類似,用于定義「值(value)」户盯,包括函數(shù)值嵌施。

lazy val max = (x: Int, y: Int) => if (x > y) x else y 

其次饲化,它又具有def的語義,它不會在定義max時就完成求值吗伤。但是滓侍,它與def不同,它會在第一次使用max時完成值的定義牲芋,對于以后再次使用max將返回相同的函數(shù)值撩笆。

參數(shù)傳遞

Scala存在兩種參數(shù)傳遞的方式。

  • Pass-by-Value:按值傳遞
  • Pass-by-Name:按名傳遞

按值傳遞

默認情況下缸浦,Scala的參數(shù)是按照值傳遞的夕冲。

def and(x: Boolean, y: Boolean) = x && y

對于如下調(diào)用語句:

and(false, s.contains("horance"))

表達式s.contains("horance")首先會被立即求值,然后才會傳遞給參數(shù)y裂逐;而在and函數(shù)體內(nèi)再次使用y時歹鱼,將不會再對s.contains("horance")表達式求值,直接獲取最先開始被求值的結果卜高。

傳遞函數(shù)

將上例and實現(xiàn)修改一下弥姻,讓其具有函數(shù)類型的參數(shù)。

def and(x: () => Boolean, y: () => Boolean) = x() && y()

其中掺涛,() => Boolean等價于Function0[Boolean]庭敦,表示參數(shù)列表為空,返回值為Boolean的函數(shù)類型薪缆。

調(diào)用方法時秧廉,傳遞參數(shù)必須顯式地加上() =>的函數(shù)頭。

and(() => false, () => s.contains("horance"))

此時拣帽,它等價于如下實現(xiàn):

and(new Function0[Boolean] { 
  def apply(): Boolean = false
}, new Function0[Boolean] {
  def apply(): Boolean = s.contains("horance")
}

此時疼电,and方法將按照「按值傳遞」將Function0的兩個對象引用分別傳遞給了xy的引用變量。但時减拭,此時它們函數(shù)體蔽豺,例如s.contains("horance"),在參數(shù)傳遞之前并沒有被求值拧粪;直至在and的方法體內(nèi)修陡,xy調(diào)用了apply方法時才被求值。

也就是說既们,and方法可以等價實現(xiàn)為:

def and(x: () => Boolean, y: () => Boolean) = x.apply() && y.apply()

按名傳遞

通過Function0[R]的參數(shù)類型濒析,在傳遞參數(shù)前實現(xiàn)了延遲初始化的技術。但實現(xiàn)中啥纸,參數(shù)傳遞時必須構造() => R的函數(shù)值号杏,并在調(diào)用點上顯式地加上()完成apply方法的調(diào)用,存在很多的語法噪聲。

因此盾致,Scala提供了另外一種參數(shù)傳遞的機制:按名傳遞主经。按名傳遞略去了所有()語法噪聲。例如庭惜,函數(shù)實現(xiàn)中罩驻,xy不用顯式地加上()便可以完成調(diào)用。

def and(x: => Boolean, y: => Boolean) = x && y

其次护赊,調(diào)用點用戶無需構造() => R的函數(shù)值惠遏,但它卻擁有延遲初始化的功效。

and(false, s.contains("horance"))

借貸模式

資源回收是計算機工程實踐中一項重要的實現(xiàn)模式骏啰。對于具有GC的程序設計語言节吮,它僅僅實現(xiàn)了內(nèi)存資源的自動回收,而對于諸如文件IO判耕,數(shù)據(jù)庫連接透绩,Socket連接等資源需要程序員自行實現(xiàn)資源的回收。

該問題可以形式化地描述為:給定一個資源R壁熄,并將資源傳遞給用戶空間帚豪,并回調(diào)算法f: R => T;當過程結束時資源自動釋放草丧。

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

因此狸臣,該實現(xiàn)模式也常常被稱為「借貸模式」,是保證資源自動回收的重要機制方仿。本文通過using的抽象控制固棚,透視Scala在這個領域的設計技術,以便鞏固「按名傳遞」技術的應用仙蚜。

控制抽象:using

import scala.language.reflectiveCalls

object using {
  type Closeable = { def close(): Unit }

  def apply[T <: Closeable, R](resource: => T)(f: T => R): R = {
    var source = null.asInstanceOf[T]
    try {
      source = resource
      f(source)
    } finally {
      if (source != null) source.close
    }
  }
}

客戶端

例如如下程序,它讀取用戶根目錄下的README.md文件厂汗,并傳遞給using委粉,using會將文件句柄回調(diào)給用戶空間,用戶實現(xiàn)文件的逐行讀热㈣搿贾节;當讀取完成后,using自動關閉文件句柄衷畦,釋放資源栗涂,但用戶無需關心這個細節(jié)。

import scala.io.Source
import scala.util.Properties

def read: String = using(Source.fromFile(readme)) { 
  _.getLines.mkString(Properties.lineSeparator)
}

鴨子編程

type Closeable = { def close(): Unit }定義了一個Closeable的類型別名祈争,使得T必須是具有close方法的子類型斤程,這是Scala支持「鴨子編程」的一種重要技術。例如菩混,File滿足T類型的特征忿墅,它具有close方法扁藕。

惰性求值

resource: => T是按照by-name傳遞,在實參傳遞形參過程中疚脐,并未對實參進行立即求值亿柑,而將求值推延至resource: => T的調(diào)用點。

對于本例棍弄,using(Source.fromFile(source))語句中望薄,Source.fromFile(source)并沒有馬上發(fā)生調(diào)用并傳遞給形參,而將求值推延至source = resource語句呼畸。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末式矫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子役耕,更是在濱河造成了極大的恐慌采转,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞬痘,死亡現(xiàn)場離奇詭異故慈,居然都是意外死亡,警方通過查閱死者的電腦和手機框全,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門察绷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人津辩,你說我怎么就攤上這事拆撼。” “怎么了喘沿?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵闸度,是天一觀的道長。 經(jīng)常有香客問我蚜印,道長莺禁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任窄赋,我火速辦了婚禮哟冬,結果婚禮上,老公的妹妹穿的比我還像新娘忆绰。我一直安慰自己浩峡,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布错敢。 她就那樣靜靜地躺著翰灾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上预侯,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天致开,我揣著相機與錄音,去河邊找鬼萎馅。 笑死双戳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的糜芳。 我是一名探鬼主播飒货,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼峭竣!你這毒婦竟也來了塘辅?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤皆撩,失蹤者是張志新(化名)和其女友劉穎扣墩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扛吞,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡呻惕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了滥比。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亚脆。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盲泛,靈堂內(nèi)的尸體忽然破棺而出濒持,到底是詐尸還是另有隱情,我是刑警寧澤寺滚,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布柑营,位于F島的核電站,受9級特大地震影響玛迄,放射性物質(zhì)發(fā)生泄漏由境。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一蓖议、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讥蟆,春花似錦勒虾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春愕宋,著一層夾襖步出監(jiān)牢的瞬間玻靡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工中贝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留囤捻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓邻寿,卻偏偏與公主長得像蝎土,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绣否,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容