在尝试理解伴侣对象时,我编写了以下代码来计算实例化类的次数。我不得不使用' 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
答案 0 :(得分: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的大多数介绍可能会以此为例)。
但是,它很可能最终会比仅使用计数器的可变变量更复杂。事实上,我通常在函数式语言中使用可变变量用于这些“全局递增计数器”,这样我就可以这样做。
答案 1 :(得分:3)
这是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项目。
答案 2 :(得分: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