Scala流、备忘录和占位符语法

Scala流、备忘录和占位符语法,scala,Scala,最近我一直在玩Scala infinite Streams,我注意到一个奇怪的行为。这个想法是为了证明记忆可以与声明为val的流一起工作 具有以下测试套件: import org.scalatest.{Matchers, FunSuite} class StreamsSuite extends FunSuite with Matchers { test("natural numbers stream, proving memoization") { var hitCounter =

最近我一直在玩Scala infinite 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)
  }
}
一切都很完美,正如预期的那样。但是,当我以以下方式将流定义更改为使用下划线占位符语法时:

lazy val Naturals: Stream[Int] = 1 #:: Naturals.map {
  hitCounter += 1
  _ + 1
}
关于流内容的所有断言仍然有效,但是
hitCounter
将只更新一次(并以值1结束)

我认为有一些优化正在发生,比如Scala端,一种内联,它抑制了clojure主体中的任何副作用。有人能解释吗


Scala 2.11.7版

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

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)
在第二个版本中,您看到的效果更加清晰
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>
scala>val f1:Int=>Int={println(“foo”);\uu1}
福
f1:Int=>Int=
scala>valf2:Int=>Int=i=>{println(“foo”);i+1}
f2:Int=>Int=

在第一种情况下,括号及其内容是一个计算为函数的块,而在第二种情况下,括号及其内容是一个计算为函数结果的块。

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

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)
在第二个版本中,您看到的效果更加清晰
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>
scala>val f1:Int=>Int={println(“foo”);\uu1}
福
f1:Int=>Int=
scala>valf2:Int=>Int=i=>{println(“foo”);i+1}
f2:Int=>Int=

在第一种情况下,方括号及其内容是一个计算结果为函数的块,而在第二种情况下,它们是函数结果的块。

花括号中使用的占位符语法不是函数,而是一个代码块,它将函数作为结果返回。表达式计算一次,因此任何副作用只发生一次

大括号中使用的占位符语法不是一个函数,而是一个代码块,它将函数作为结果返回。表达式计算一次,因此任何副作用只发生一次

谢谢你的回答!你激励我做更彻底的调查。您的解释缺少的一点是占位符表达式作为结果返回一个函数,因此它只能计算一次。我把它添加到我自己对这个问题的回答中。谢谢你的回答!你激励我做更彻底的调查。您的解释缺少的一点是占位符表达式作为结果返回一个函数,因此它只能计算一次。我在自己对这个问题的回答中添加了它。这并不准确,因为占位符语法不需要大括号,而且说表达式“返回”结果有点奇怪。在
列表(1).map(+1)
中,
+1
是一个使用占位符语法定义函数的表达式。没有花括号,而且
\u1
不会“返回”它计算为1的函数。这并不准确,因为占位符语法不需要花括号,而且说表达式“返回”结果有点奇怪。在
列表(1).map(+1)
中,
+1
是一个使用占位符语法定义函数的表达式。没有大括号,
\u1
不会“返回”它计算为1的函数。