One of my friends contacted me with a request to review and improve some Scala code. Here is how we’ll go about correcting this code and making it fit the functional programming style.

def getConfigSafely[T](config: Config, field: String, dataType: Class[T]): Option[T] = {
    if (config.hasPath(field)) {
      val ret = dataType match {
        case dt if dt == classOf[Config] => config.getConfig(field)
        case dt if dt == classOf[String] => config.getString(field)
        case dt if dt == classOf[Int] => config.getInt(field)
        //TODO: Handle more list types.
        case dt if dt == classOf[List[String]] => config.getStringList("inputs").asScala.toList
        //TODO: Handle Map data types in a better way
        case dt if dt == classOf[Map[Any, Any]] => {
          config.getConfig(field).entrySet()
            .asScala
            .map(e => (e.getKey, e.getValue.render().replace("\"", ""))).toMap
        }
      }
      Some(ret.asInstanceOf[T])
    }
    else {
      None
    }
}

def getConfigOrFail[T](config: Config, field: String, dataType: Class[T]): T = {
  if (config.hasPath(field)) {
    val ret = dataType match {
      case dt if dt == classOf[Config] => config.getConfig(field)
      case dt if dt == classOf[String] => config.getString(field)
      case dt if dt == classOf[Int] => config.getInt(field)
      //TODO: Handle more list types.
      case dt if dt == classOf[List[String]] => config.getStringList("inputs").asScala.toList
      case dt if dt == classOf[Map[Any, Any]] => {
        config.getConfig(field).entrySet()
          .asScala
          .map(e => (e.getKey, e.getValue.render().replace("\"", ""))).toMap
      }
    }
    ret.asInstanceOf[T]
  }
  else {
    throw new ConfigurationException(s"$field is not a valid entry.")
  }
}

So the first thing i see in the current implementation is ret.asInstanceOf[T] and throw new ConfigurationException(s"$field is not a valid entry.") which is a clear indication of code smell, and also the shape of the code look like more java than scala.

My first motivation will be to remove class cast asInstanceOf and throw which is usually forbidden and replace by an Either or a MonadError to represent the possibility of the failure of the computation.

First remove class cast

def getConfigOrFail[T](config: Config, field: String, dataType: Class[T]): T

If we look at the signature of the method, we can see this parameter dataType: Class[T] is used to do class cast, but why in the first place it is needed ? because TypeSafe Config is not type safe :)

For each specific type String / Int / List / Float / Long / … you need to call a different method

val config= ConfigFactory.load();
config.getString(path)
config.getInt(path)
...

So the goal is to remove all this pattern matching and ugly class cast and replace it by using type class + implicits, one of the main feature of scala.

sealed trait Get[T]{
  def get(c: Config, path: String): T
}

implicit val stringGet = new Get[String] {
 override def get(c: Config, path: String): String = c.getString(path)
}

// you can provide many Get instances to support any typedef 
get[T](config: Config, path: String)(implicit g: Get[T]): T = {
  g.get(config, path)
}

//How to use it, and voilaaa
get[String](config, "mypath")

Remove throw / make safe

So the current code is not safe at all config.getXXX it is unsafe code since it can throw some exception like ConfigException . In scala the simplest way is to use Try to catch any computation error.

sealed trait Get[T] {
  def get(c: Config, path: String): Try[T]
}

//wrap unsafe code with Try
implicit val stringGet = new Get[String] {
 override def get(c: Config, path: String): Try[String] = Try(c.getString(path))
}

def get[T](config: Config, path: String)(implicit g: Get[T]): Try[T] = {
  g.get(config, path)
}

Remove duplication

So the current implementation is more extensible and much safer than the previous code but we are missing the ability to have different effects Option / Either

In the previous implementation we add two similar methods :

getConfigSafely() and getConfigOrFail() are unsafe method (not a pure function) since we are throwing Exception .

we construct our programs using only pure functions — in other words, functions that have no side effects. What are side effects? A function has a side effect if it does something other than simply return a result, for example:

  • Modifying a variable
  • Modifying a data structure in place
  • Setting a field on an object
  • Throwing an exception or halting with an error
  • Printing to the console or reading user input
  • Reading from or writing to a file
  • Drawing on the screen

So how to make it safe ??? Not really hard don’t throw any Exception, and use an effect to represent this error like the monad Either (we could also use Try[+T] but it hide the definition of the Error, so it is usually better to use Either[Err, T])

def getConfigOrFail[T](config: Config, field: String, dataType: Class[T]): Either[Throwable, T] = ???

So we could have something like this with the new implementation

sealed trait Get[T]{
  def get(c: Config, path: String): Try[T]
}

//wrap unsafe code with Try
implicit val stringGet = new Get[String]{
  override def get(c: Config, path: String): Try[String] = Try(c.getString(path))
}

def get[T](config: Config, path: String)(implicit g: Get[T]): Option[T] = {
  g.get(config, path).toOption
}

def get[T](config: Config, path: String)(implicit g: Get[T]): Either[Throwable,T] = {
  g.get(config, path).toEither
}

As we can see both methods looks like very similar, what if we could refactor and only have one method get . So in scala Either[E,T] and Option[T] can be expressed in a generic manner with a type constructor which call type constructor like F[_], but if you pay attention in our case we are only interested in one type constructor define by a single underscore _ . Since Either need two type constructor arguments Either[_,_], we will need to remove one type argument. So here the solution.

// option can be expressed since we only have one type
Option[T] == F[_] 

//create a new type/alias of Either where we fixed Left side as 'Throwable'\
type ErrorOf[T] = Either[Throwable, T]
// we can now express Either as Single Type ErrorOf[T] == F[_]

It exists another way to deal with this problem, by using sbt plugin kind-projector and give the ability do not have to declare a new type

New definition of the method with 2 Type parameters

F[_] represent the effect return type and T value return type

def get[F[_], T](config: Config, path: String)(implicit g: Get[T]): F[T] = ???

So now we now how to express in a generic manner Either and Option we need the ability to convert Try => Option and Try => Either . So to be able to manage this conversion we need again type classes

//functionK is a natural transformation that allow you to convert `F[_] => G[_] in our case F[_] will be a fix type Try[_]`

sealed trait FunctionK[F[_], G[_]] {
  def apply[A](fa: F[A]): G[A]
}

//convert a Try to an Option
implicit val functionKOption = new FunctionK[Try, Option] {
  override def apply[T](t: Try[T]): scala.Option[T] = {
    t.toOption
  }
}

type ErrorOf[T] = Either[Throwable, T] 

//convert a Try to an Either\
implicit val functionKEither = new FunctionK[Try, ErrorOf] {
  override def apply[T](t: Try[T]): ErrorOf[T] = {
    t.toEither
  }
}

So i provided for each F[_] type a dedicated functionk instance, to do the conversion. As you can see we are only interested for our use case to convert Try[_] into another type constructor like Option[_] or ErrorOf[_] = Either[Throwable,_] or any another data type.

Here the final version of the code, just need to provide an instance for each Effect type Either and Option.

sealed trait FunctionK[F[_], G[_]] {
  def apply[A](fa: F[A]): G[A]
}

implicit val functionKOption = new FunctionK[Try, Option] {
  override def apply[T](t: Try[T]): scala.Option[T] = {
    t.toOption
  }
}

type ErrorOf[T] = Either[Throwable, T]

implicit val functionKEither = new FunctionK[Try, ErrorOf] {
  override def apply[T](t: Try[T]): ErrorOf[T] = {
    t.toEither
  }
}

sealed trait Get[T] {
  def get(c: Config, path: String): Try[T]
}

implicit val stringGet = new Get[String] {
  override def get(c: Config, path: String): Try[String] = Try(c.getString(path))
}

def get[F[_], T](config: Config, path: String)(implicit functionK: FunctionK[Try, F], g: Get[T]): F[T] = {
  functionK(g.get(config, path))
}

//how to use it 
val valWithError: ErrorOf[String] = get[ErrorOf, String](config, "my.path")
val valWithOption: Option[String] = get[Option, String](config, "my.path")

Conclusion

So we learn how to use type classes and also type constructor, which allow us to improve the code and make it more type safe. Have abstraction to support any kind of effect through FunctionK type classes.

The project is available here if you want to test it =>

https://github.com/regis-leray/safe-config