1

I'm using the following interface from a Java library:

// This is a Java class from a library I'm using
public interface Listener {
  void receiveConfigInfo(final String configInfo);
}

And I'm extending it as follows, in Scala:

//MyClass needs to extends the Listener interface
final class MyClass extends Listener {
  private var config = Option.empty

  // Implement the the Listener interface
  override def receiveConfigInfo(configInfo: String): Unit = {
    try {
      config  = decode[Map[String, String]](configInfo) match {
        case Right(config) => Some(config)
        case Left(_) => None
      }
    } catch {
      case _: Throwable => nacosConfig = None
    }
  }

  override def getConfig():Option[Map[String, String]] = nacosConfig
}

receiveConfigInfo will be called in automatically whenever relevant.

getConfig returns the latest value of configuration.

Is there a way to make config into a val, rather than a mutable var? I cannot change the signature of receiveConfigInfo, as it needs to respect the signature of parent class.

The objective is whenever I call getConfig, I should get latest config value. However my current implementation has a var which is not good, is there any way to change this code to make it val or another way if possible?

stefanobaghino
  • 11,253
  • 4
  • 35
  • 63
Arjun Karnwal
  • 379
  • 3
  • 13
  • There is no _(simple)_ way to remove `var` there. Well, you may use [**Deferred** ](https://typelevel.org/cats-effect/api/cats/effect/concurrent/Deferred.html) from `cats-effect`, but that will change a lot the gay to use your code. I would rather focus on removing the `try` for a [**Try**](https://www.scala-lang.org/api/current/scala/util/Try.html) and that `None[Map[String, String]]` is not valid syntax and it is not necessary. – Luis Miguel Mejía Suárez Feb 19 '20 at 03:03
  • The Class needs to implement the interface not extend. – Raja Shekar Feb 19 '20 at 04:39
  • 1
    @RajaShekar The example unfortunately mixes Java and Scala together, but in Scala you use `extend` for anything, including Java interfaces (as long as they are the first thing you extend, the keyword `with` must be used for following traits and interfaces). – stefanobaghino Feb 19 '20 at 08:20
  • Side note: don't catch `Throwable`s, I'd recomment using the `NonFatal` extractor instead (more details in this answer: https://stackoverflow.com/questions/29744462/the-difference-between-nonfatal-and-exception-in-scala). – stefanobaghino Feb 19 '20 at 08:25

2 Answers2

0

Given that the Java interface you're implementing seems to require an implementation to accept arbitrarily many new configInfos, I'd suggest that the most honest thing is to leave the var but encapsulated such that it can only be changed via receiveConfigInfo.

So perhaps something like

object MyClass {
  private[MyClass] Config {
    private var underlying: Map[String, String] = Map.empty

    override def get(key: String): Option[String] = 
      synchronized { underlying.get(key) }

    override def toMap: Map[String, String] =
      synchronized { underlying }

    override def update(configInfo: String): Unit =
      synchronized {
        // I'm assuming that decode is somewhere statically accessible
        // I'm also assuming that a function returning an `Either` won't throw non-fatal exceptions... if it can return a Left, a Right, or throw, then:
        //   Try { decode[...](configInfo) }.flatMap(_.left.map(msg => new RuntimeException(msg)).toTry)
        val decoded =
          decode[Map[String, String]](configInfo)
            .left
            .map(_ => new RuntimeException("decode failed")
            .toTry
        decoded.foreach { newConfig => underlying = newConfig }
        decoded.recoverWith { _ =>
          underlying = Map.empty
          decoded
        }
      }
  }
}

class MyClass extends Listener {
  private val config = new MyClass.Config

  override def receiveConfigInfo(configInfo: String): Unit = config.update(configInfo)
}

With a solution along these lines, the configuration is immutable to anything in MyClass that doesn't call update (which is hopefully apparent enough vs. assigning to a var.

I'm preserving the behavior of unsetting the config if a bad configInfo comes along; it may make more sense to leave configInfo unchanged in that case, which would make it even more difficult in practice to inadvertently change the config except by a client of MyClass calling receiveConfigInfo with a valid config.

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
0

If you really want to use a val, you can use one of the mutable structures in collection.mutable. However, it would be more roundabout, possibly prone to error, and behind the scenes, actually using var, so you might as well directly use var (the way your code already is).

// in class structure
val config = collection.mutable.IndexedSeq[Option[Map[String, String]]](None)
...
// in receiveConfigInfo
config(0) = decode ...

// in getConfig
config(0)
ELinda
  • 2,658
  • 1
  • 10
  • 9