scala中的延迟函数定义

时间:2010-08-25 03:09:06

标签: scala design-patterns lazy-loading lazy-evaluation lazy-initialization

我一直在学习scala,我得说它是一种非常酷的语言。我特别喜欢它的模式匹配功能和函数文字,但我来自javascript,ruby背景,这些语言中我最喜欢的模式之一是惰性函数和方法定义模式。 javascript中的一个例子是

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

带有小调整的相同代码在ruby中工作,您只需使用单例对象在执行计算后重新定义该方法。当涉及昂贵的计算时,这种事情非常方便,如果您需要结果,则提前不知道。我知道在scala中我可以使用缓存来模拟相同类型的结果,但我正在尝试避免条件检查,到目前为止,我的实验已经返回了负面结果。有没有人知道scala中是否存在惰性函数或方法定义模式?

注意:javascript代码来自Peter Michaux的site

6 个答案:

答案 0 :(得分:30)

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被称为名称参数,您可以将其视为“给我一些将根据请求生成字符串的内容”。请注意,如果您使用它两次,它将每次重新生成,这是Randall Schultz的便利模式所在:

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

现在只有在你需要它时才会生成(通过by-name参数)存储它并在需要时重新使用它(通过懒惰的val)。

所以这样做,不是JavaScript的方式,即使你可以使Scala看起来很像JavaScript。

答案 1 :(得分:19)

Scala有lazy val s,除非使用val,否则不会对其初始值设定项进行求值。延迟val可以用作方法局部变量。

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一个或更多次)。

答案 2 :(得分:10)

您可以定义一个lazy 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 :(得分:6)

我认为一些响应者对你提出这个问题的方式感到有些困惑。您想要的Scala构造是一个简单的惰性定义:

lazy val foo = new java.util.Date

Date对象的构造最多只发生一次,并推迟到第一次引用foo。

答案 4 :(得分: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

答案 5 :(得分:2)

我认为你的意思是“懒函数”是函数文字或匿名函数。

在Scala中你可以做这样的事情,非常类似于你发布的javascript代码。

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

    foo()
}

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

主要区别在于:

  • 您无法重新分配外部foo
  • 没有“function”关键字,而是使用类似(s:String)=>的内容。 {代码}
  • 最后一个语句是块的返回值,因此您不需要添加“return”。