scalafunctional-programmingpurely-functionalreferential-transparency

Functional listener with state


Let's say in my pure Scala program i have a dependency to a Java service. This Java service accepts a listener to notify me when some data changes.

Let's say the data is a tuple(x, y) and the java service calls the listener whenever X or Y changes but i'm interested only when X.

For this my listener has to save the last value of X, and forward the update/call only when oldX != X, so in order to have this my impure scala listener implementation has to hold a var oldX

val listener = new JavaServiceListener() {

 var oldX;
 def updated(val x, val y): Unit = {
  if (oldX != x) {
     oldX = x
    //do stuff
  }
}

javaService.register(listener)

How would i go about to design a wrapper for this kind of thing in Scala without val or mutable collections ? I can't at the JavaServiceListener level since i'm bound by the method signature, so I need another layer above which the java listener forwards to somehow


Solution

  • I found the solution I like with Cats and Cats-Effect:

    trait MyListener {
      def onChange(n: Int): Unit
    }
    
    class MyDistinctFunctionalListener(private val last: Ref[IO, Int], consumer: Int => Unit) extends MyListener {
      override def onChange(newValue: Int): Unit = {
        val program =
          last
            .getAndSet(newValue)
            .flatMap(oldValue => notify(newValue, oldValue))
    
        program.unsafeRunSync()
      }
    
      private def notify(newValue: Int, oldValue: Int): IO[Unit] = {
        if (oldValue != newValue) IO(consumer(newValue)) else IO.delay(println("found duplicate"))
      }
    }
    
    object MyDistinctFunctionalListener {
      def create(consumer: Int => Unit): IO[MyDistinctFunctionalListener] =
        Ref[IO].of(0).map(v => new MyDistinctFunctionalListener(v, consumer))
    }
    
    val printer: Int => Unit = println(_)
    
    val functionalDistinctPrinterIO =  MyDistinctFunctionalListener.create(printer)
    
    functionalDistinctPrinterIO.map(fl =>
      List(1, 1, 2, 2, 3, 3, 3, 4, 5, 5).foreach(fl.onChange)
    ).unsafeRunSync()
    

    More stuff about handling shared state here https://github.com/systemfw/scala-italy-2018

    it is debatable if this is worth it over the private var solution