Scala Streams,memoization和占位符语法

Mac*_*ski 1 scala

我最近一直在玩Scala无限Streams,我注意到了一个奇怪的行为.这个想法是为了证明memoization与Streams声明为一个val.

有以下测试套件:

import org.scalatest.{Matchers, FunSuite}

class StreamsSuite extends FunSuite with Matchers {
  test("natural numbers stream, proving memoization") {
    var hitCounter = 0
    lazy val Naturals: Stream[Int] = 1 #:: Naturals.map { n =>
      hitCounter += 1
      n + 1
    }

    Naturals.take(3).toIndexedSeq should be(Seq(1, 2, 3))
    hitCounter should be(2)
    Naturals.take(3).toIndexedSeq
    hitCounter should be(2)
    Naturals.take(4).toIndexedSeq
    hitCounter should be(3)
  }
}
Run Code Online (Sandbox Code Playgroud)

一切都完美和预期.但是,当我将Stream定义更改为以下列方式使用下划线占位符语法时:

lazy val Naturals: Stream[Int] = 1 #:: Naturals.map {
  hitCounter += 1
  _ + 1
}
Run Code Online (Sandbox Code Playgroud)

关于Stream内容的所有断言仍将保留,但hitCounter只会更新一次(并以值1结束).

我认为有一种优化发生的是Scala方面,一种内联,它可以抑制clojure体内的任何副作用.任何人都可以解释?

Scala版本2.11.7

Tra*_*own 6

以下两个表达式是等效的:

scala> List(1, 2, 3).map { println("foo"); _ + 1 }
foo
res0: List[Int] = List(2, 3, 4)

scala> List(1, 2, 3).map({ println("foo"); _ + 1 })
foo
res1: List[Int] = List(2, 3, 4)
Run Code Online (Sandbox Code Playgroud)

你看到的效果在第二版中更清晰一些.map只是一个将函数作为参数的方法,当你给它一个包含多个表达式的块时,它会立即(只有一次)计算块,就像任何其他表达式一样.

非占位符情况的不同之处在于箭头后的任何副作用都发生在函数内部.采取以下两个定义:

scala> val f1: Int => Int = { println("foo"); _ + 1 }
foo
f1: Int => Int = <function1>

scala> val f2: Int => Int = i => { println("foo"); i + 1 }
f2: Int => Int = <function1>
Run Code Online (Sandbox Code Playgroud)

在第一个中,括号及其内容是一个计算函数的块,而在第二个中它们是一个作为函数结果的块.