12

我一直在學習scala,我得說這是一種非常酷的語言。我特別喜歡它的模式匹配功能和函數文字,但是我來自javascript,ruby背景,並且在這些語言中我最喜歡的模式之一是懶惰函數和方法定義模式。在JavaScript的一個例子是scala中的懶惰函數定義

var foo = function() { 
    var t = new Date(); 
    foo = function() { 
    return t; 
    }; 
    return foo(); 
}; 

相同的代碼稍作調整工作在紅寶石,你只需要使用單獨的對象重新定義法進行計算後。當涉及昂貴的計算時,這種事情非常方便,而且如果您需要結果,您不會提前知道。我知道在scala中我可以使用緩存來模擬同樣的結果,但我試圖避免條件檢查,到目前爲止,我的實驗已經返回負面結果。有誰知道在scala中是否有懶惰的函數或方法定義模式?

注意:JavaScript代碼來自Peter Michaux的site

+1

請記住,除非真的沒有回答你的問題,否則你應該給答案一個漂亮的綠色複選標記! – 2010-08-25 17:38:21

+0

+1鏈接到Peter Michaux網站上非常有趣的文章。 :-) – 2010-12-01 08:18:09

回答

25

JavaScript中所有複雜的代碼似乎只是試圖緩存日期的值。在Scala中,可以實現平凡同樣的事情:

lazy val foo = new Date 

而且,如果甚至不希望做一個VAL,但要調用是否需要它只會執行昂貴代碼的函數,你可以

def maybeExpensive(doIt: Boolean, expensive: => String) { 
    if (doIt) println(expensive) 
} 
maybeExpensive(false, (0 to 1000000).toString) // (0 to 1000000).toString is never called! 
maybeExpensive(true, (0 to 10).toString)  // It is called and used this time 

,該模式expensive: => String被稱爲由名稱參數,你能想到的是,「給我的東西,將產生上要求的字符串。」請注意,如果你使用它的兩倍,它在每次的時間,這就是蘭德爾·舒爾茨的方便模式來在重新生成:

def maybeExpensiveTwice(doIt: Boolean, expensive: => String) { 
    lazy val e = expensive 
    if (doIt) { 
    println(e) 
    println("Wow, that was " + e.length + " characters long!") 
    } 
} 

現在你只生成如果你需要它(通過按姓名參數)存儲它,並重新使用它,如果你再次需要它(通過懶惰val)。

所以這樣做,而不是JavaScript的方式,即使你可能使Scala看起來很像JavaScript。

+1

我只是想弄清楚在scala中實現相同結果的習慣用法,就像你指出的那樣,懶惰的vals和名字參數是scala中的方法。 – davidk01 2010-08-26 07:21:45

+0

最後一部分似乎並不正確。我測試了第二部分,如果您傳遞doIt參數爲true,那麼每次都會調用昂貴的函數。你可以通過傳遞這個函數來測試它def expensive = {println(「I Get Called」); 「return value」}然後用(1到3)測試它.foreach(_ => maybeExpensiveTwice(true,expensive)) – Reza 2013-12-08 14:16:22

+0

@Reza - 我的意思是它只被調用一次_per方法invocation_儘管在該方法中使用了兩次。當然,如果您多次調用該方法,則會在調用該方法時多次調用該方法。 (爲了避免你需要將它緩存在更高的層次上。) – 2013-12-15 21:28:04

19

斯卡拉有lazy val s,其初始值不被評估,除非和直到val被使用。惰性vals可以用作方法局部變量。

Scala還具有名稱方法參數,其實際參數表達式被包裝在一個thunk中,並且每次在方法體中引用正式參數時都會評估thunk。

這些可以用來實現惰性評估語義,比如Haskell中的默認值(至少在我對Haskell非常有限的理解中)。

def meth(i: => Int): Something = { 
    //  ^^^^^^ by-name parameter syntax 
    lazy val ii = i 
    // Rest of method uses ii, not i 
} 

在該方法中,作爲實際參數的表達式將被評估任一零次(如果該方法體的動態執行路徑從不使用ii)或一次(如果它使用ii一次或多次) 。

+0

感謝您提供關於名稱參數和惰性val的信息,我相信他們會在簡化我的一些代碼時派上用場。至於我原來的問題,事實證明,斯卡拉允許類似JavaScript重新定義的函數變量,所以幾乎相同的代碼工作。我所有的原始測試都有語法錯誤,導致我無法獲得與javascript中相同的結果。 – davidk01 2010-08-25 04:23:32

+2

@ davidk01 - 但即使可以,你也不想用JavaScript的方式來做。在Scala中有更多更乾淨的方法來實現同樣的事情! – 2010-08-25 14:37:20

+0

當使用這種技術時,有沒有一種方法可以明確地清除方法外部的val?我問自己什麼時候val會被GC清理。一般的GC行爲是清楚的,但是使用技術可能會讓人困惑,因爲這種方法可以作爲函數傳遞並存儲。我處於一種情況,這種技術非常有用,但是在執行完一個特定的點後,我想清理閥值,但我不想清理周圍的物體。任何方式來實現這一點,或者這是不可能的,因爲它是一個val? – user573215 2013-10-14 13:35:04

2

我認爲你的意思是「懶功能」是函數文字或匿名函數。

在斯卡拉你可以做這樣的事情,非常類似於你發佈的JavaScript代碼。

val foo =() => { 
    val t = new Date() 
    val foo =() => {t} 

    foo() 
} 

println ("Hello World:" + foo()) 

的主要區別在於:

  • 你不能重新分配外富
  • 沒有「功能」關鍵字,而不是你使用類似(S:字符串)= > {code}
  • 最後一條語句是塊的返回值,所以你不需要添加「return」。
10

您可以定義一個懶惰的VAL這是一種功能:

lazy val foo = { 
    val d = new Date 
() => { d } 
} 

println(foo()) 

foo()現在每次返回相同的Date對象,這將是首次初始化FOO被稱爲對象。

爲了解釋一下代碼,第一次foo()被調用{ val d = new Date;() => { d } }被執行,d被賦值給一個新的日期值,然後它評估最後一個表達式() => { d }並將其賦值給foo值。那麼foo是一個沒有返回d的參數的函數。

3

我不知道什麼關於Ruby,但Scala有單獨的對象模式也:

Welcome to Scala version 2.8.0.r22634-b20100728020027 (Java HotSpot(TM) Client VM, Java 1.6.0_20). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> object LazyInit {          
    |  val msec = { println("Hi,I'm here!"); System.currentTimeMillis } 
    | } 
defined module LazyInit 

scala> System.currentTimeMillis            
res0: Long = 1282728315918 

scala> println(System.currentTimeMillis +" : " + LazyInit.msec)    
Hi,I'm here! 
1282728319929 : 1282728319930 

scala> println(System.currentTimeMillis +" : " + LazyInit.msec) 
1282728322936 : 1282728319930 

scala> println(System.currentTimeMillis +" : " + LazyInit.msec) 
1282728324490 : 1282728319930 

scala> 

如果你想要得到的功能,你可以把它的功能類型的子類型:

scala> object LazyFun extends (() => Long) {    
    |  val msec = System.currentTimeMillis   
    |  def apply() = msec       
    | } 
defined module LazyFun 

scala> System.currentTimeMillis       
res2: Long = 1282729169918 

scala> println(System.currentTimeMillis + " : " + LazyFun()) 
1282729190384 : 1282729190384 

scala> println(System.currentTimeMillis + " : " + LazyFun()) 
1282729192972 : 1282729190384 

scala> println(System.currentTimeMillis + " : " + LazyFun()) 
1282729195346 : 1282729190384 
6

我認爲一些響應者對你說的這個問題的方式有些困惑。斯卡拉構建你想在這裏是一個簡單的懶人定義:

lazy val foo = new java.util.Date 

Date對象的建設將至多出現一次被推遲,直到第一次引用FOO。