Chain style + scala
I have configurer
one that supports chain style like this:
val configurer = Configurer("init").propA("a").propB(3).propC("bla-bla")
it is a third party library which I cannot change.
And i have
case class Config (propA: Option [String], propB: Option [Int], propC: Option [String])
Now I need to build my configurer
with a given object config
, the method propX
should be called if the corresponding value is set to config
.
What's the best way to do this in a functional way?
I do not like this
val configurer = Configurer("init")
val withPropA = config.propA.map(configurer.propA).getOrElse(configure)
val withPropB = config.propB.map(configurer.propB).getOrElse(withPropA)
val withPropC = config.propC.map(configurer.propC).getOrElse(withPropB)
Just feel like there must be an elegant way.
You can do this with var, which is usually a sign of bad code in scala, but in this case I find it perfectly acceptable.
def buildConfigurer(propA: Option[String], propB: Option[Int], propC: Option[String]) = {
var configurer = new Configurer("init")
propA.foreach(a => configurer = configurer.propA(a))
propB.foreach(b => configurer = configurer.propB(b))
propC.foreach(c => configurer = configurer.propC(c))
configurer
}
Since you specifically asked about this in a functional way, I would suggest using a fold on each option that converts Some
to the function you want and None
to identity
:
config.propA.fold(identity[Configurer] _)(a => _ propA a) andThen
config.propB.fold(identity[Configurer] _)(b => _ propB b) andThen
config.propC.fold(identity[Configurer] _)(c => _ propC c)
If you're really adventurous, you can make it a little more elegant with Scalaz:
import scalaz._, Scalaz._
config.propA.map(a => Endo[Configurer](_ propA a)).orZero |+|
config.propB.map(b => Endo[Configurer](_ propB b)).orZero |+|
config.propC.map(c => Endo[Configurer](_ propC c)).orZero
In real code, you probably want to use Eugene's solution, though, since you're just wrapping an API that's not perfect and it's important that it's clear here.
I would use something like @EugeneZhulenev's solution, but Option.fold
instead foreach
stayed the same (without going over to the higher order / scalar version suggested by @TravisBrown):
def buildConfigurer(cfg: Config): Configurer = {
val with0 = new Configurer("init")
val withA = cfg.propA.fold(with0)(with0.propA(_))
val withB = cfg.propB.fold(withA)(withA.propB(_))
val withC = cfg.propC.fold(withB)(withB.propC(_))
withC
}