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
1
  • Functors or State Monad seems like a good fit Commented Dec 1, 2016 at 1:30

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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 ?
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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.