8

在尝试理解伴随对象时,我编写了以下代码来计算类被实例化的次数。我不得不使用'var'来保持计数。是否有一种“函数式编程”方式来实现相同的任务,即使用不可变变量。

class C {
  C.counter+=1
  def someCFunction = {println ("some C function. Counter is "+C.counter)}
}

object C{
  var counter:Int=0 //I do not want to use var
}

val c1 = new C
c1.someCFunction

val c2 = new C
c2.someCFunction
4

3 回答 3

5

避免可变变量和其他副作用的纯函数程序的一大特性是表达式计算的值仅取决于表达式本身。它不取决于评估事物的顺序(从左到右、从右到左、严格、惰性)、操作系统的状态、一天中的时间等。

特别是,这意味着在纯函数设置中,每次调用都new C将返回一个完全相同的计数器对象。这通常是一件好事,因为它可以更容易地推理你的程序,但它会妨碍你在那里尝试做的事情。要使 C 对象与众不同,您需要显式传递它们的计数器值,老实说,这只是在解决问题。

val c1 = new C(0)
val c2 = new C(1)

如果你想拥有一个像内部类变量一样的全局“计数器”变量,你正在使用一种可能的方法来在纯函数设置中实现它,将计数器值传递给每个需要计数器的函数并让这些函数也返回计数器的更新版本。举个简单的例子:

def increment_counter(n: Int): Int = { n + 1)

def create_c(n: Int): (C, Int) = {
    val c = new C(n)
    val n' = increment_counter n
    (c, n')
}

val n = 0
val (c1, n') = create_c(n)
val (c2, n'') = create_c(n')
val n' = increment_counter(n)

您可以使用 State Monad 模式更好地构造它(大多数对 monad 的介绍可能都会以此为例)。

但是,它很可能最终会比仅使用可变变量作为计数器更复杂。事实上,我通常在允许我这样做的功能语言中为这些“全局递增计数器”使用可变变量。

于 2016-11-30T20:18:54.540 回答
5

这是 State Monad 的一个很好的用例。您无需就地修改变量,而是创建一个新值并将其传递。

import cats.data.State
class C {}
object C { val counter: State[Int, Unit] = State.pure() }

def createNewC: State[Int, C] = {
  // increment the count, and return a new instance of C
  C.counter.modify(_ + 1).map(_ => new C)
}

val countAll = for {
  c0 <- createNewC
  c1 <- createNewC
  c2 <- createNewC
  c3 <- createNewC
} yield {
  // use your instance of C in here
  ()
}

// actually run your program, start the counter at 0
countAll.run(0).value._1 // 4

注意:这里的状态来自 Cats 项目。

于 2016-11-30T20:40:21.320 回答
2

从本质上讲,这并不是var一件坏事。这是有原因的。只是它所代表的东西,一个保持某种形式的可变状态的实体,应该尽可能避免。在无法避免的情况下,如果设计需要运行的类实例总数,则应尽可能限制其范围。

class C private {  // private constructor, can only use factory method
  def someCFunction = {println ("some C function. Counter is "+ C.getCount())}
}
object C{
  private[this] var counter:Int = 0    // not even companions can see this
  def apply() = {counter += 1; new C}  // factory method
  def getCount() = counter             // accessor method
}

val c1 = C()
c1.someCFunction

val c2 = C()
c2.someCFunction
于 2016-11-30T20:19:30.507 回答