15

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

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

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

注意:javascript 代码来自 Peter Michaux 的网站

4

6 回答 6

32

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。

于 2010-08-25T14:35:30.407 回答
20

Scala 有lazy vals,除非使用 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一次或多次)。

于 2010-08-25T03:25:08.713 回答
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。

于 2010-08-25T06:00:37.753 回答
6

我认为一些回答者对你提出问题的方式有点困惑。您在这里想要的 Scala 构造是一个简单的惰性定义:

lazy val foo = new java.util.Date

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

于 2010-08-25T09:39:08.230 回答
4

我对 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
于 2010-08-25T09:22:13.283 回答
2

我认为您的意思是“惰性函数”是函数文字或匿名函数。

在 Scala 中,您可以做这样的事情,与您发布的 javascript 代码非常相似。

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

    foo()
}

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

主要区别在于:

  • 您无法重新分配外部 foo
  • 没有“function”关键字,而是使用类似 (s:String) => {code}
  • 最后一个语句是一个块的返回值,所以你不需要添加“return”。
于 2010-08-25T05:51:41.437 回答