我已经configurer
支持链样式,如下所示:
val configurer = Configurer("init").propA("a").propB(3).propC("bla-bla")
它的第三方库,我无法改变。
我有我的
case class Config(propA:Option [String],propB:Option [Int],propC: 选项[字符串])
现在我需要使用给定的configurer
对象构建我的config
,如果propX
中设置了相应的值,则应调用方法config
。
以功能性方式做到这一点的最佳方法是什么?
我不喜欢这个
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)
觉得应该有一种优雅的方式。
答案 0 :(得分:2)
由于您特别要求以功能方式执行此操作,因此我建议在每个选项上使用折叠,将Some
转换为所需的函数,将None
转换为identity
:< / p>
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)
如果你真的喜欢冒险,你可以用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
在实际代码中,你可能想要使用Eugene的解决方案,因为你只是包装了一个不理想的API,重要的是从现在开始明确。
答案 1 :(得分:1)
你可以用var来做,通常它是scala中代码不好的标志,但在这种情况下我觉得它绝对可以接受。
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
}
答案 2 :(得分:1)
我会使用@ EugeneZhulenev的解决方案,但Option.fold
而不是foreach
保持不变(不需要转到@TravisBrown提出的高阶/ scalaz版本):
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
}