【问题标题】:Scala: elegant way to use require to validate multiple options?Scala:使用 require 验证多个选项的优雅方式?
【发布时间】:2015-05-08 01:40:30
【问题描述】:

我有两个解决这个问题的方法。我不喜欢他们两个,所以我想知道是否有更优雅的解决方案。

import java.util.Date
import scala.math.Ordered.orderingToOrdered

// Solution # 1:
case class A(startDate: Option[Date] = None,
             endDate: Option[Date]   = None) {
  require(if (startDate.isEmpty && endDate.isEmpty) false else true, 
          "Either startDate or endDate must be defined")
  require(if (startDate.isDefined && endDate.isDefined) startDate.get < endDate.get else true,  
          s"startDate:${startDate.get} must be less than endDate:${endDate.get}")
  // Problem: multiple checks using isEmpty and isDefined followed by .get
}

// Solution # 2:
case class B(startDate: Option[Date] = None,
             endDate: Option[Date]   = None) {
  val (requirement, msg) = (startDate, endDate) match {
    case (None, None)                  => false -> "Either startDate or endDate must be defined"
    case (Some(s), Some(e)) if (s > e) => false -> s"startDate:$s must be less than endDate:$e"
    case _                             => true  -> "OK" // Problem: redundant statement
  }
  require(requirement, msg)
}

条件:

  1. startDate 或 endDate 都可以是 None
  2. startDate 不能大于 endDate

【问题讨论】:

  • scalaz 是一个选项吗?您可以从选项更改为验证
  • @JustinPihony 是的。可以举个例子吗?

标签: scala functional-programming coding-style pattern-matching require


【解决方案1】:

我会这样写:

import java.util.Date

case class MyTest(startDate: Option[Date] = None, endDate: Option[Date] = None) {
  require(startDate.isDefined || endDate.isDefined,
    "Either startDate or endDate must be defined")
  require(!(startDate.isDefined && endDate.isDefined) || (startDate.get.before(endDate.get)),
    s"startDate: ${startDate.get} must be less than endDate:${endDate.get}")
}

object Test extends App {
  // Gives "Either startDate or endDate must be defined" as expected
  //val m1 = MyTest(None, None)

  // These run OK
  val m2 = MyTest(Some(new Date(1234)), None)
  val m3 = MyTest(None, Some(new Date(4321)))
  val m4 = MyTest(Some(new Date(1234)), Some(new Date(4321)))

  // Gives "startDate: Thu Jan 01 01:00:00 CET 1970 must be less than endDate: Thu Jan 01 01:00:00 CET 1970" as expected
  //val m4 = MyTest(Some(new Date(4321)), Some(new Date(1234)))
}

requires 中不需要这些if。还要注意第二个require 使用"not(a) or b" is equivalent to "a => b"。最后但同样重要的是,当您检查要定义的选项时,在您的选项上执行 .get 时您是安全的。

【讨论】:

  • 这对眼睛特别不利。
【解决方案2】:

只是ensuring 的一个小 craigslist 广告:

scala> val a = Option(2); val b = Option(3)
a: Option[Int] = Some(2)
b: Option[Int] = Some(3)

scala> (for (x <- a; y <- b) yield { require(x < y); y - x }).ensuring(_.nonEmpty)
res0: Option[Int] = Some(1)

scala> val a = Option(42)
a: Option[Int] = Some(42)

scala> (for (x <- a; y <- b) yield { require(x < y); y - x }).ensuring(_.nonEmpty)
java.lang.IllegalArgumentException: requirement failed
  at scala.Predef$.require(Predef.scala:207)
  at $anonfun$1$$anonfun$apply$1.apply$mcII$sp(<console>:10)
  at $anonfun$1$$anonfun$apply$1.apply(<console>:10)
  at $anonfun$1$$anonfun$apply$1.apply(<console>:10)
  at scala.Option.map(Option.scala:146)
  at $anonfun$1.apply(<console>:10)
  at $anonfun$1.apply(<console>:10)
  at scala.Option.flatMap(Option.scala:171)
  ... 33 elided

scala> val a: Option[Int] = None
a: Option[Int] = None

scala> (for (x <- a; y <- b) yield { require(x < y); y - x }).ensuring(_.nonEmpty)
java.lang.AssertionError: assertion failed
  at scala.Predef$.assert(Predef.scala:151)
  at scala.Predef$Ensuring$.ensuring$extension2(Predef.scala:255)
  ... 33 elided

对于您的用例,将您的约束放在自定义应用中:

scala> :pa
// Entering paste mode (ctrl-D to finish)

case class C(i: Int, j: Int)
object C {
  def apply(a: Option[Int] = None, b: Option[Int] = None) = (
    for (x <- a; y <- b) yield {
      require(x < y, "Order violation")
      new C(x, y)
    }
  ).ensuring(_.nonEmpty, "Missing value").get
}

// Exiting paste mode, now interpreting.

defined class C
defined object C

scala> C(Option(42))
java.lang.AssertionError: assertion failed: Missing value
  at scala.Predef$Ensuring$.ensuring$extension3(Predef.scala:256)
  at C$.apply(<console>:13)
  ... 33 elided

scala> C(Option(42),Option(3))
java.lang.IllegalArgumentException: requirement failed: Order violation
  at scala.Predef$.require(Predef.scala:219)
  at C$$anonfun$apply$1$$anonfun$apply$2.apply(<console>:12)
  at C$$anonfun$apply$1$$anonfun$apply$2.apply(<console>:12)
  at scala.Option.map(Option.scala:146)
  at C$$anonfun$apply$1.apply(<console>:12)
  at C$$anonfun$apply$1.apply(<console>:12)
  at scala.Option.flatMap(Option.scala:171)
  at C$.apply(<console>:12)
  ... 33 elided

scala> C(Option(2),Option(3))
res5: C = C(2,3)

编辑:只需要一个参数。

scala> :pa
// Entering paste mode (ctrl-D to finish)

case class C(a: Option[Int] = None, b: Option[Int] = None) {
  require(a orElse b nonEmpty, "No value")
  for (x <- a; y <- b) require(x < y, "Order violation")
}

// Exiting paste mode, now interpreting.

warning: there was one feature warning; re-run with -feature for details
defined class C

scala> C()
java.lang.IllegalArgumentException: requirement failed: No value
  at scala.Predef$.require(Predef.scala:219)
  ... 34 elided

scala> C(Option(42))
res1: C = C(Some(42),None)

scala> C(Option(42),Option(3))
java.lang.IllegalArgumentException: requirement failed: Order violation
  at scala.Predef$.require(Predef.scala:219)
  at C$$anonfun$1$$anonfun$apply$mcVI$sp$1.apply$mcVI$sp(<console>:9)
  at C$$anonfun$1$$anonfun$apply$mcVI$sp$1.apply(<console>:9)
  at C$$anonfun$1$$anonfun$apply$mcVI$sp$1.apply(<console>:9)
  at scala.Option.foreach(Option.scala:257)
  at C$$anonfun$1.apply$mcVI$sp(<console>:9)
  at C$$anonfun$1.apply(<console>:9)
  at C$$anonfun$1.apply(<console>:9)
  at scala.Option.foreach(Option.scala:257)
  ... 34 elided

scala> C(Option(2),Option(3))
res3: C = C(Some(2),Some(3))

【讨论】:

  • 虽然这个答案几乎是正确的行,但我希望 C(None,Option(3)) 不会抛出 java.lang.AssertionError: assertion failed: Missing value.. 问题来自使用 for-comprehension 后跟 .get
  • @VenkatSudheerReddyAedama 对不起 tldr;实际上是因为ensuring;我看错了你的问题。
猜你喜欢
  • 1970-01-01
  • 2011-10-03
  • 2019-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多