Scala 傳名參數(shù)

2018-09-28 18:25 更新

傳名參數(shù)

上篇我們使用柯里化函數(shù)定義一個(gè)控制機(jī)構(gòu) withPrintWriter,它使用時(shí)語法調(diào)用有如 Scala 內(nèi)置的控制結(jié)構(gòu):

val file = new File("date.txt")
withPrintWriter(file){
  writer => writer.println(new java.util.Date)
}

不過仔細(xì)看一看這段代碼,它和 scala 內(nèi)置的 if 或 while 表達(dá)式還是有些區(qū)別的,withPrintWrite r的{}中的函數(shù)是帶參數(shù)的含有“writer=>”。 如果你想讓它完全和 if 和 while 的語法一致,在 Scala 中可以使用傳民參數(shù)來解決這個(gè)問題。

:我們知道通常函數(shù)參數(shù)傳遞的兩種模式,一是傳值,一是引用。而這里是第三種按名稱傳遞。

下面我們以一個(gè)具體的例子來說明傳名參數(shù)的用法:

var assertionsEnabled=true
def myAssert(predicate: () => Boolean ) =
  if(assertionsEnabled && !predicate())
    throw new AssertionError

這個(gè) myAssert 函數(shù)的參數(shù)為一個(gè)函數(shù)類型,如果標(biāo)志 assertionsEnabled 為 True 時(shí),mymyAssert 根據(jù) predicate 的真假?zèng)Q定是否拋出異常,如果 assertionsEnabled 為 false,則這個(gè)函數(shù)什么也不做。

這個(gè)定義沒什么問題,但調(diào)用起來看起來卻有些別扭,比如:

myAssert(() => 5 >3 )

還需要 ()=>,你可以希望直接使用 5>3,但此時(shí)會(huì)報(bào)錯(cuò):

scala> myAssert(5 >3 )
<console>:10: error: type mismatch;
 found   : Boolean(true)
 required: () => Boolean
              myAssert(5 >3 )

此時(shí),我們可以把按值傳遞(上面使用的是按值傳遞,傳遞的是函數(shù)類型的值)參數(shù)修改為按名稱傳遞的參數(shù),修改方法,是使用=>開始而不是 ()=>來定義函數(shù)類型,如下:

def myNameAssert(predicate:  => Boolean ) =
  if(assertionsEnabled && !predicate)
    throw new AssertionError

此時(shí)你就可以直接使用下面的語法來調(diào)用 myNameAssert:

myNameAssert(5>3)

此時(shí)就和 Scala 內(nèi)置控制結(jié)構(gòu)一樣了,看到這里,你可能會(huì)想我為什么不直接把參數(shù)類型定義為 Boolean,比如:

def boolAssert(predicate: Boolean ) =
  if(assertionsEnabled && !predicate)
    throw new AssertionError

調(diào)用也可以使用

boolAssert(5>3)

和 myNameAssert 調(diào)用看起來也沒什么區(qū)別,其實(shí)兩者有著本質(zhì)的區(qū)別,一個(gè)是傳值參數(shù),一個(gè)是傳名參數(shù),在調(diào)用 boolAssert(5>3)時(shí),5>3 是已經(jīng)計(jì)算出為 true,然后傳遞給 boolAssert 方法,而 myNameAssert(5>3),表達(dá)式 5>3 沒有事先計(jì)算好傳遞給 myNameAssert,而是先創(chuàng)建一個(gè)函數(shù)類型的參數(shù)值,這個(gè)函數(shù)的 apply 方法將計(jì)算5>3,然后這個(gè)函數(shù)類型的值作為參數(shù)傳給 myNameAssert。

因此這兩個(gè)函數(shù)一個(gè)明顯的區(qū)別是,如果設(shè)置 assertionsEnabled 為 false,然后試圖計(jì)算 x/0 ==0,

scala> assertionsEnabled=false
assertionsEnabled: Boolean = false
scala> val x = 5
x: Int = 5
scala> boolAssert ( x /0 ==0)
java.lang.ArithmeticException: / by zero
  ... 32 elided
scala> myNameAssert ( x / 0 ==0)

可以看到 boolAssert 拋出 java.lang.ArithmeticException: / by zero 異常,這是因?yàn)檫@是個(gè)傳值參數(shù),首先計(jì)算 x /0 ,而拋出異常,而 myNameAssert 沒有任何顯示,這是因?yàn)檫@是個(gè)傳名參數(shù),傳入的是一個(gè)函數(shù)類型的值,不會(huì)先計(jì)算 x /0 ==0,而在 myNameAssert 函數(shù)體內(nèi),由于 assertionsEnabled 為 false,傳入的 predicate 沒有必要計(jì)算(短路計(jì)算),因此什么也不會(huì)打印。如果我們把 myNameAssert 修改下,把 predicate 放在前面:

scala> def myNameAssert1(predicate:  => Boolean ) =
     |   if( !predicate && assertionsEnabled )
     |     throw new AssertionError
myNameAssert1: (predicate: => Boolean)Unit
scala> myNameAssert1 ( x/0 ==0)
java.lang.ArithmeticException: / by zero
  at $anonfun$1.apply$mcZ$sp(<console>:11)
  at .myNameAssert1(<console>:9)
  ... 32 elided

這個(gè)傳名參數(shù)函數(shù)也拋出異常(你可以想想是為什么?)

前面的 withPrintWriter 我們暫時(shí)沒法使用傳名參數(shù),去掉 writer=>,否則就難以實(shí)現(xiàn)“租賃模式”,不過我們可以看看下面的例子,設(shè)計(jì)一個(gè) withHelloWorld 控制結(jié)構(gòu),這個(gè) withHelloWorld 總打印一個(gè)“hello,world”

import scala.io._
import java.io._
def withHelloWorld ( op: => Unit) {
  op   
  println("Hello,world")
}
val file = new File("date.txt")
withHelloWorld{
  val writer=new PrintWriter(file)
  try{
   writer.println(new java.util.Date)
  }finally{
    writer.close()
  }
}
withHelloWorld {
  println ("Hello,Guidebee")
} 

Hello,world 
Hello,Guidebee
Hello,world

可以看到 withHelloWorld 的調(diào)用語法和 Scala 內(nèi)置控制結(jié)構(gòu)非常象了。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)