函数式编程风格的编码指南说,我们不应该在Scala中使用null
或var
来获得更好的函数式编程代码。
我想执行如下操作
var a = 2
if(condition==true){
a = a * 3 /*someOperation*/
}
if(condition2==true){
a = a * 6 /*someOperation*/
}
if(condition3==true){
a = a * 8 /*someOperation*/
}
val b = a * 2/*someOperation*/
那么现在如何避免在这种情况下使用 var 并将其替换为 val?
避免具有多个条件var
的最简单方法是使用临时值
val a1 = 2
val a2 = if (condition) a1*3 else a1
val a3 = if (condition2) a2*6 else a2
val a = if (condition3) a3*8 else a3
val b = a * 2/*someOperation*/
在实际代码中,您将a1
、a2
和a3
有意义的名称来描述每个处理阶段的结果。
如果您担心在范围内有这些额外的值,请将其放在一个块中:
val a = {
val a1 = 2
val a2 = if (condition) a1*3 else a1
val a3 = if (condition2) a2*6 else a2
if (condition3) a3*8 else a3
}
更新
如果您想要一种功能更强大的方法,请将条件和修改收集在一起并依次应用它们,如下所示:
val mods: List[(Boolean, Int=>Int)] = List(
(condition, _*3),
(condition2, _*6),
(condition3, _*8)
)
val a = mods.foldLeft(2){ case (a,(cond, mod)) => if (cond) mod(a) else a }
这实际上只适用于条件或修改更复杂的情况,并且将它们放在一起会使代码更清晰。
val a = 2 * (if (condition) 3 else 1)
val b = 2 * a
或者,也许...
val a = 2
val b = 2 * (if (condition) a*3 else a)
这取决于在这些操作之后是否/如何使用a
。
我认为您可能过于简化了示例,因为我们知道编写代码时a
的值,因此您可以像这样写出来:
val a = if (condition) 2 else 6
val b = a * 2
假设您的实际操作更复杂并且无法像那样预先计算,那么您可能会发现这样的模式匹配是一种更好的方法:
val a = (condition, 2) match {
case (true, z) =>
z * 3
case (false, z) =>
z
}
val b = a * 2
您可以尝试以下方法:
type Modification = Int => Int
type ModificationNo = Int
type Conditions = Map[ModificationNo, Boolean]
val modifications: List[(Modification, ModificationNo)] =
List[Modification](
a => a * 3,
a => a * 6,
a => a * 8
).zipWithIndex
def applyModifications(initial: Int, conditions: Conditions): Int =
modifications.foldLeft[Int](initial) {
case (currentA, (modificationFunc, modificationNo)) =>
if (conditions(modificationNo)) modificationFunc(currentA)
else currentA
}
val a: Int = applyModifications(initial = 2,
conditions = Map(0 -> true, 1 -> false, 2 -> true))
它可能看起来很复杂,但如果条件数量足够大,这种方法可以提供额外的灵活性。 现在,当您必须添加更多条件时,您无需编写新的 if 语句和进一步重新分配给var
。只需将修改函数添加到现有列表中
没有一个完美的解决方案。
有时,如果var
简化了代码并限制了单个函数的范围,则可以使用它。
话虽如此,这就是我以功能方式执行此操作的方式:
val op1: Int => Int =
if (condition1) x => x * 3
else identity
val op2: Int => Int =
if (condition2) x => x * 6
else identity
val op3: Int => Int =
if (condition3) x => x * 8
else identity
val op = op1 andThen op2 andThen op3
// can also be written as
// val op = Seq(op1, op2, op3).reduceLeft(_ andThen _)
val a = 2
val b = op(a) * 2
最简单的方法是将变量包装成monad,以便您.map
它。最简单的monad是一个Option
,所以你可以写:
val result = Option(a).map {
case a if condition => a*2
case a => a
}.map {
case a if condition2 => a*6
case a => a
}.fold(a) {
case a if condition3 => a*8
case a => a
}
(最后一个操作是fold
而不是map
,因此您最终得到结果的"原始"值,而不是选项。等效地,您可以将其编写为.map
,然后在末尾添加.getOrElse(a)
(。
当您有许多这样的条件操作,或者许多必须重复该模式的用例时,将它们放入列表中,然后遍历该列表可能会有所帮助:
def applyConditionals[T](toApply: (() => Boolean, T => T)*) = toApply
.foldLeft(a) {
case (a, (cond, oper)) if cond() => oper(a)
case (a, _) => a
}
val result = applyConditionals[Int] (
(() => condition, _ * 2),
(() => condition2, _ * 6),
(() => condition3, _ * 8)
)
重要的一点是,FP是一种全新的编程范式。它是如此根本的不同,以至于有时您无法摘录imperative
代码并尝试将其转换为functional
代码。
这种差异不仅适用于代码,也适用于解决问题的思维方式。函数式编程要求你从链式数学计算的角度来思考,这些计算或多或少是相互独立的(这意味着这些数学计算中的每一个都不应该改变其自身环境之外的任何内容(。
函数式编程完全避免了状态的突变。因此,如果您的解决方案需要具有变量x
该变量该变量在某一点具有值10
,而在另一点具有其他值100
...那么你的解决方案不是functional
.而且您不能为not functional
的解决方案编写function
代码。
现在,如果你看一下你的案例(假设你实际上不需要a
2
,然后在一段时间后更改为6
(并尝试将其转换为独立的数学计算链,那么最简单的一个是:
val a = if (condition) 2 else 6
val b = a * 2