【问题标题】:Scala how to avoid var during some override conditionScala如何在某些覆盖条件下避免var
【发布时间】:2019-03-27 09:33:26
【问题描述】:

函数式编程风格的编码指南说我们不应该在 Scala 中使用 nullvar 以获得更好的函数式编程代码。

我想执行如下操作

    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 替换它?

【问题讨论】:

    标签: scala functional-programming


    【解决方案1】:

    避免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*/
    

    在实际代码中,您可以给a1a2a3 赋予有意义的名称来描述每个处理阶段的结果。

    如果您对在范围内包含这些额外值感到困扰,请将其放在一个块中:

    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 }
    

    这仅适用于条件或修改更复杂的情况,将它们放在一起可以使代码更清晰。

    【讨论】:

      【解决方案2】:
      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

      【讨论】:

      • 条件比较多怎么办
      【解决方案3】:

      我认为您可能过于简化了您的示例,因为我们在编写代码时知道 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
      

      【讨论】:

      • 条件比较多怎么办
      • @user3607698 看我的回答。
      【解决方案4】:

      您可以尝试以下方法:

      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。只需将修改函数添加到现有列表中即可

      【讨论】:

        【解决方案5】:

        重要的一点是,FP 是一种全新的编程范式。它是如此根本不同,以至于有时您无法摘录imperative 代码并尝试将其转换为functional 代码。

        这种差异不仅适用于代码,还适用于解决问题的思维方式。函数式编程要求您考虑链式数学计算,它们或多或少相互独立(这意味着这些数学计算中的每一个都不应该改变其自身环境之外的任何东西)。

        函数式编程完全避免了状态的突变。因此,如果您的解决方案需要有一个变量x,该变量在某一点具有值10,而在另一点具有其他值100...那么您的解决方案不是functional。而且您不能为not functional 的解决方案编写function 代码。

        现在,如果您查看您的案例(假设您实际上不需要将a 变为2,然后在一段时间后更改为6)并尝试将其转换为独立的数学计算链,那么最简单的就是以下,

        val a = if (condition) 2 else 6
        val b = a * 2
        

        【讨论】:

          【解决方案6】:

          没有一个完美的解决方案。
          有时可以使用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
          

          【讨论】:

            【解决方案7】:

            最简单的方法是将你的变量包装成一个 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)
            )
            

            【讨论】:

              猜你喜欢
              • 2017-01-31
              • 1970-01-01
              • 1970-01-01
              • 2018-12-23
              • 1970-01-01
              • 2021-11-10
              • 2015-05-15
              • 2020-09-13
              • 2013-06-02
              相关资源
              最近更新 更多