【问题标题】:Simplification or alternative for this Scala pattern match此 Scala 模式匹配的简化或替代方案
【发布时间】:2013-11-30 23:43:04
【问题描述】:

我已经实现了一个 Play! 2 Scala 中的 QueryStringBindable 用于 Range 类型。范围由最小值或最大值或两者组成(浮点类型)。在我的 QueryBindable 实现中,我使用 internalBinder 将两个可能的参数 min 和 max 转换为 Option[Either[String, Float]],将它们组合成一个元组,对此进行模式匹配,最后返回一个 Option[Either[String,范围]]。这可行,但正如您在下面的代码中看到的那样,模式匹配非常冗长。在 Scala 中有更简洁的方法吗? 也许以某种方式利用高阶函数来获得相同的结果结构?

import play.api.mvc.QueryStringBindable

case class Range(min: Option[Float], max: Option[Float])

object Range {

implicit def rangeQueryStringBindable(implicit intBinder: QueryStringBindable[Float]) = new QueryStringBindable[Range] {

    override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, Range]] = {

      val minOpt = intBinder.bind("min", params)
      val maxOpt = intBinder.bind("max", params)
      (minOpt, maxOpt) match {
        case (None, None) => None
        case (Some(Right(min)), Some(Right(max))) => Some(Right(Range(Some(min), Some(max))))
        case (None, Some(Right(max))) => Some(Right(Range(None, Some(max))))
        case (Some(Right(min)), None) => Some(Right(Range(Some(min), None)))
        case (Some(Left(minError)), Some(Left(maxError))) => Some(Left(minError))
        case (Some(Left(minError)), None) => Some(Left(minError))
        case (None, Some(Left(maxError))) => Some(Left(maxError))
        case (Some(Right(_)), Some(Left(maxError))) => Some(Left(maxError))
        case (Some(Left(minError)), Some(Right(_))) => Some(Left(minError))
      }
    }

    override def unbind(key: String, range: Range): String = {
      (range.min, range.max) match {
        case (Some(min), Some(max)) => intBinder.unbind("min", min) + "&" + intBinder.unbind("max", max)
        case (Some(min), None) => intBinder.unbind("min", min)
        case (None, Some(max)) => intBinder.unbind("max", max)
        case (None, None) => throw new IllegalArgumentException("Range without values makes no sense")
      }
    }
  }
}

【问题讨论】:

    标签: scala playframework-2.0 pattern-matching


    【解决方案1】:
    (minOpt,maxOpt) match {
      case (None,None) => None
      case (Some(Left(m)),_) => Some(Left(m))
      case (_,Some(Left(m))) => Some(Left(m))
      case (_,_) => Some(Right(Range(minOpt.map(_.right.get),maxOpt.map(_.right.get))))
    }
    

    【讨论】:

    • 谢谢!这样看起来确实简洁多了!
    【解决方案2】:

    通过几个函数将Option[Either[Error, A]] 转换为Either[Error, Option[A]],在我看来,您最终可以得到一些更清晰的东西。我还建议重命名 Range,因为它与 scala.collections.immutable 中的同名类冲突。

    import play.api.mvc.QueryStringBindable
    
    case class RealRange(min: Option[Float], max: Option[Float])
    
    object BindingEitherUtils {
      implicit class OptionWithEitherFlatten[A, B](value: Option[Either[A, B]]) {
        def flattenRight: Either[A, Option[B]] = {
          value.map { either =>
            either.right.map{ right => Some(right) }
          }.getOrElse{ Right(None) }
        }
      }
    
      implicit class EitherWithUnflatten[A, B](value: Either[A, Option[B]]) {
        def unflattenRight: Option[Either[A, B]] = {
          value.fold(left => Some(Left(left)), _.map{ right => Right(right) })
        }
      }
    }
    
    object RealRange {
      import BindingEitherUtils._
    
      val minError = "Invalid minimum value for RealRange"
      val maxError = "Invalid maximum value for RealRange"
    
      implicit def rangeQueryStringBindable(implicit floatBinder: QueryStringBindable[Float]) = new QueryStringBindable[RealRange] {
        override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, RealRange]] = {
          val minOpt = floatBinder.bind("min", params).flattenRight
          val maxOpt = floatBinder.bind("max", params).flattenRight
    
          minOpt.left.map{ _ => minError }.right.flatMap { min =>
            maxOpt.left.map{ _ => maxError }.right.flatMap { max =>
              (min, max) match {
                case (None, None ) =>
                  Right(None)
                case (Some(minVal), Some(maxVal)) if minVal > maxVal =>
                  Left("Minimum value is larger than maximum value")
                case _ =>
                  Right(Some(RealRange(min, max)))
              }
            }
          }.unflattenRight
        }
    
        override def unbind(key: String, range: RealRange): String = {
          (range.min, range.max) match {
            case (Some(min), Some(max)) => floatBinder.unbind("min", min) + "&" + floatBinder.unbind("max", max)
            case (Some(min), None) => floatBinder.unbind("min", min)
            case (None, Some(max)) => floatBinder.unbind("max", max)
            case (None, None) => throw new IllegalArgumentException("RealRange without values makes no sense")
          }
        }
      }
    
      def test(): Unit = {
        val binder = rangeQueryStringBindable
    
        Seq[(String, String)](
          ("10", "20"),
          ("10", null),
          (null, "10"),
          (null, null),
          ("asd", "asd"),
          ("10", "asd"),
          ("asd", "10"),
          ("asd", null),
          (null, "asd"),
          ("20", "10")
        ).foreach{ case (min, max) =>
          val params = Seq(
            Option(min).map{ m => "min" -> Seq(m) },
            Option(max).map{ m => "max" -> Seq(m) }
          ).flatten.toMap
    
          val result = binder.bind("", params)
    
          println(s"$params => $result" )
        }
      }
    }
    

    结果:

    Map(min -> List(10), max -> List(20)) =>
      Some(Right(RealRange(Some(10.0),Some(20.0))))
    Map(min -> List(10)) =>
      Some(Right(RealRange(Some(10.0),None)))
    Map(max -> List(10)) =>
      Some(Right(RealRange(None,Some(10.0))))
    Map() =>
      None
    Map(min -> List(asd), max -> List(asd)) =>
      Some(Left(Invalid minimum value for RealRange))
    Map(min -> List(10), max -> List(asd)) =>
      Some(Left(Invalid maximum value for RealRange))
    Map(min -> List(asd), max -> List(10)) =>
      Some(Left(Invalid minimum value for RealRange))
    Map(min -> List(asd)) =>
      Some(Left(Invalid minimum value for RealRange))
    Map(max -> List(asd)) =>
      Some(Left(Invalid maximum value for RealRange))
    Map(min -> List(20), max -> List(10)) =>
      Some(Left(Minimum value is larger than maximum value))
    

    【讨论】:

    • 有趣的方法,但就可读性而言,我更喜欢@Marth 的解决方案。
    【解决方案3】:

    是的,可以简化。

    对于bind 方法,您可以放置​​一些通配符,当您遇到错误时可以简化它。这样,Range 汇编逻辑只有 4 个排列。我不会在这里做太多的魔法,因为这会使理解您的代码变得复杂。

    override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, Range]] = {
    
      val minOpt = intBinder.bind("min", params)
      val maxOpt = intBinder.bind("max", params)
    
      (minOpt, maxOpt) match {
        case (None, None) => None
        case (Some(Right(min)), Some(Right(max))) => Some(Right(Range(Some(min), Some(max))))
        case (None, Some(Right(max))) => Some(Right(Range(None, Some(max))))
        case (Some(Right(min)), None) => Some(Right(Range(Some(min), None)))
        // Error handling
        case (Some(Left(minError)), _) => Some(Left(minError))
        case (_, Some(Left(maxError))) => Some(Left(maxError))
      }
    }
    

    对于解除绑定,我将使用不同的方法,通过利用Option 的映射函数,然后将它们组合成一个Iterable,您可以调用mkString,它不会对1 个字符串执行任何操作并附加一个@987654327 @如果有两个字符串。代码示例有类型,便于理解。

    def unbind(key: String, range: Range): String = {
      val minString: Option[String] = range.min.map(min => intBinder.unbind("min", min))
      val maxString: Option[String] = range.max.map(max => intBinder.unbind("max", max))
      val strings: Iterable[String] = minString ++ maxString
      strings match {
        case Nil => throw new IllegalArgumentException("Range without values makes no sense")
        case _ => strings.mkString("&")
      }
    }
    

    如果你喜欢短代码:

    def unbind(key: String, range: Range): String = {
      val minString = range.min.map(min => intBinder.unbind("min", min))
      val maxString = range.max.map(max => intBinder.unbind("max", max))
      minString ++ maxString match {
        case Nil => throw new IllegalArgumentException("Range without values makes no sense")
        case strings => strings.mkString("&")
      }
    }
    

    【讨论】:

    • 喜欢你改进版的“unbind”!但是对于“绑定”部分,@Marth 的解决方案更短,并且可以节省 2 个案例。
    • 是的,我同意!没想到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-06
    • 2016-08-13
    • 2016-12-27
    • 2011-09-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多