给出以下代码:
case class Config(
addThree: Boolean = true,
halve: Boolean = true,
timesFive: Boolean = true
)
def doOps(num: Integer, config: Config): Integer = {
var result: Integer = num
if ( config.addThree ) {
result += 3
}
if ( config.halve ) {
result /= 2
}
if ( config.timesFive ) {
result *= 5
}
result
}
val config = Config(true,false,true)
println( doOps(20, config) )
println( doOps(10, config) )
我想用更有效和惯用的构造替换丑陋的doOps方法。具体来说,我想构建一系列函数,这些函数仅根据所使用的特定Config执行所需的转换。我知道我可能想创建一些部分应用的函数,我可以将Integer传递给它,但是我在如何以有效的方式实现这一点上画了一个空白。
我特别想避免doOps中的if语句,我希望得到的结构只是一个函数链,它可以在不检查条件的情况下调用链中的下一个函数。
结果代码,我想会看起来像这样:
case class Config(
addThree: Boolean = true,
halve: Boolean = true,
timesFive: Boolean = true
)
def buildDoOps(config: Config) = ???
val config = Config(true,false,true)
def doOps1 = buildDoOps(config)
println( doOps1(20) )
println( doOps1(10) )
答案 0 :(得分:3)
这是我的建议。基本上我创建了一系列彼此独立的函数。如果其中一个操作被禁用,我将其替换为identity
。最后我foldLeft
在该序列上,使用num
参数作为初始值:
case class Config(
addThree: Boolean = true,
halve: Boolean = true,
timesFive: Boolean = true
) {
private val funChain = Seq[Int => Int](
if(addThree) _ + 3 else identity _,
if(halve) _ / 2 else identity _,
if(timesFive) _ * 5 else identity _
)
def doOps(num: Int) = funChain.foldLeft(num){(acc, f) => f(acc)}
}
我将doOps()
置于Config
内,因为它非常适合。
Config(true, false, true).doOps(10) //(10 + 3 ) * 5 = 65
如果你是受虐狂,foldLeft()
可以这样写:
def doOps(num: Int) = (num /: funChain){(acc, f) => f(acc)}
如果您不喜欢identity
,请使用Option[Int => Int]
和flatten
:
private val funChain = Seq[Option[Int => Int]](
if(addThree) Some(_ + 3) else None,
if(halve) Some(_ / 2) else None,
if(timesFive) Some(_ * 5) else None
).flatten
答案 1 :(得分:2)
与Tomasz Nurkiewicz的解决方案类似,但使用Scalaz的monoid用于endomorphisms(具有相同输入和输出类型的函数)。
monoid的追加操作是compose
,而identity元素是identity
函数。
import scalaz._, Scalaz._
def endo(c: Config): Endo[Int] =
c.timesFive ?? Endo[Int](_ * 5) |+|
c.halve ?? Endo[Int](_ / 2) |+|
c.addThree ?? Endo[Int](_ + 3)
def doOps(n: Int, c: Config) = endo(c)(n)
??
运算符在左操作数为true
时返回右操作数,在false
时返回monoid的标识元素。
请注意,函数的组合顺序与它们的应用顺序相反。
答案 2 :(得分:0)
您可以向Config
案例类添加更多功能,如下所示。这将允许您按照提到的方式将函数调用链接在一起。
case class Config(
doAddThree : Boolean = true,
doHalve : Boolean = true,
doTimesFive : Boolean = true
) {
def addThree(num : Integer) : Integer = if(doAddThree) (num+3) else num
def halve(num : Integer) : Integer = if(doHalve) (num/2) else num
def timesFive(num : Integer) : Integer = if(doTimesFive) (num*5) else num
}
def doOps(num: Integer, config: Config): Integer = {
var result: Integer = num
result = config.addThree(result)
result = config.halve(result)
result = config.timesFive(result)
result
}
val config = Config(true,false,true)
def doOps1(num : Integer) = doOps(num, config)
println( doOps1(20) )
println( doOps1(10) )
更简洁的方法是“链接”使用foldLeft
覆盖部分应用的函数列表,类似于其他答案之一所提到的:
def doOps(num: Integer, config: Config): Integer = {
List(
config.addThree(_),
config.halve(_),
config.timesFive(_)
).foldLeft(num) {
case(x,f) => f(x)
}
}
答案 3 :(得分:0)
如果你想要更具说明性(和可扩展性)的风格,你可以这样做:
import collection.mutable.Buffer
abstract class Config {
protected def Op( func: Int => Int )( enabled: Boolean) {
if ( enabled ) {
_ops += func
}
}
private lazy val _ops = Buffer[Int => Int]()
def ops: Seq[Int => Int] = _ops
}
def buildDoOps(config: Config): Int => Int = {
val funcs = config.ops
if ( funcs.isEmpty ) identity // Special case so that we don't compose with identity everytime
else funcs.reverse.reduceLeft(_ andThen _)
}
现在您可以像这样简单地定义配置:
case class MyConfig(
addThree: Boolean = true,
halve: Boolean = true,
timesFive: Boolean = true
) extends Config {
Op(_ + 3)(addThree)
Op(_ / 3)(halve)
Op(_ * 5)(timesFive)
}
最后这里是REPL中的一些测试:
scala> val config = new MyConfig(true,false,true)
config: MyConfig = MyConfig(true,false,true)
scala> val doOps1 = buildDoOps(config)
doOps1: Int => Int = <function1>
scala> println( doOps1(20) )
115
scala> println( doOps1(10) )
65
请注意buildDoOps
采用Config
的实例,这是抽象的。换句话说,它适用于Config
的任何子类(例如上面的MyConfig
),在创建其他类型的配置时,您不需要重写它。
此外,buildDoOps
返回一个只执行请求操作的函数,这意味着每次应用函数时我们都不会不必要地测试配置中的值(但仅在构造它时)。实际上,假设函数仅依赖于配置的状态,我们可以(也可能应该)为它定义lazy val
,直到Config
(这是result
价值低于):
abstract class Config {
protected def Op( func: Int => Int )( enabled: Boolean) {
if ( enabled ) {
_ops += func
}
}
private lazy val _ops = Buffer[Int => Int]()
def ops: Seq[Int => Int] = _ops
lazy val result: Int => Int = {
if ( ops.isEmpty ) identity // Special case so that we don't compose with identity everytime
else ops.reverse.reduceLeft(_ andThen _)
}
}
然后我们会这样做:
case class MyConfig(
addThree: Boolean = true,
halve: Boolean = true,
timesFive: Boolean = true
) extends Config {
Op(_ + 3)(addThree)
Op(_ / 3)(halve)
Op(_ * 5)(timesFive)
}
val config = new MyConfig(true,false,true)
println( config.result(20) )
println( config.result(10) )