8

While trying to understand companion objects, I have written following code which counts the number of times a class was instantiated. I had to use a 'var' to keep the count. Is there a 'functional programming' way to achieve the same task i.e. use immutable variables.

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
Manu Chadha
  • 15,555
  • 19
  • 91
  • 184

3 Answers3

5

One of the big properties of a purely functional program that avoids mutable variables and other side effects is that the value that an expression evaluates to depends only on the expression itself. It does not depend on what order things are evaluated (left to right, right to left, strict, lazy), the state of the operating system, time of day, etc.

In particular, this means that in a purely functional setting every call to new C will return a completely identical counter object. This is usually a good thing because it makes it easier to reason about your program but it kind of gets in the way of what you are trying to do there. To make the C objects be different you would need to explicitly pass their counter values, which to be honest, is just sweeping the problem under the rug.

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

If you want to have a global "counter" variable like the internal class variable you were using one possible way to implement it in a purely functional setting would be to pass the counter value to every function that needs a counter and have those functions also return the updated version of the counter. For a brief example:

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)

You can structure this a bit better with a State Monad pattern (most introductions to monads probably will have this as an example).

However, it is very possible that it will end up more complicated than just using a mutable variable for the counter instead. In fact, I normally use mutable variables for these "globally incrementing counters" in the functional languages that allow me to do so.

hugomg
  • 68,213
  • 24
  • 160
  • 246
5

This is a good use case for the State Monad. Instead of modifying a variable in place, you create a new value, and pass it around.

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

Note: State here comes from the Cats project.

soote
  • 3,240
  • 1
  • 23
  • 34
  • 1
    Like this your state is `Unit` which doesn't make much sense. I think you want `State[Int, Unit]` instead and use something like `State.modify(_ + 1)` in the `countCalls` function ? – Peter Neyens Nov 30 '16 at 20:49
2

It's not that var is, by its nature, a bad thing. It's in the language for a reason. It's just that what it represents, an entity that maintains some form of changeable state, should be avoided when possible. When it can't be avoided, if the design demands a running total of class instances, then its scope should be as restricted as possible.

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
jwvh
  • 50,871
  • 7
  • 38
  • 64