2013-11-30 3 views
2

Я реализовал игру! 2 QueryStringBindable в Scala для типа Range. Диапазон состоит из минимального или максимального значения или обоих (типа Float). В моей реализации QueryBindable я использую innerBinder, чтобы преобразовать два возможных параметра min и max в Option [Либо [String, Float]], объединить их в кортеж, выполнить сопоставление шаблона над этим и, наконец, вернуть параметр [Либо [String, Ассортимент]]. Это работает, но, как вы можете видеть в приведенном ниже коде, совпадение шаблонов очень много. Есть ли более лаконичный способ сделать это в Scala? Возможно ли использовать функции более высокого порядка, чтобы как-то вернуть ту же структуру результатов?Упрощение или альтернатива для этого соответствия шаблону 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") 
     } 
    } 
    } 
} 

ответ

0
(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)))) 
} 
+0

Спасибо! Это выглядит намного более кратким! – user3053314

0

С парой функций для преобразования 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)) 
+0

Интересный подход, но с точки зрения читаемости я предпочитаю решение от @Marth. – user3053314

0

Да, это может быть упрощена.

Для метода bind вы можете поместить несколько подстановочных знаков, если у вас есть ошибки, чтобы упростить его. Таким образом, у вас есть только 4 перестановки для логики сборки 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))) 
    // Error handling 
    case (Some(Left(minError)), _) => Some(Left(minError)) 
    case (_, Some(Left(maxError))) => Some(Left(maxError)) 
    } 
} 

Для отвязать я хотел бы использовать другой подход, используя функцию карты Option «s, а затем объединяя их в Iterable вы можете вызвать mkString и он не будет делать ничего, за 1 строку, и добавить &, если есть две строки. Пример кода имеет типы, поэтому вы можете понять проще.

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("&") 
    } 
} 
+0

Как и ваша улучшенная версия «unbind»! Но для части «bind» решение от @Marth еще короче и экономит еще 2 случая. – user3053314

+0

Да, согласен! Не думал об этом. –

Смежные вопросы