Scala:使用 require 验证多个选项的优雅方式?
Scala: elegant way to use require to validate multiple options?
这个问题我有两个解决方案。我不喜欢他们两个,所以我想知道是否有更优雅的解决方案。
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)
}
条件:
- startDate 或 endDate 都可以是 None
- 开始日期不能大于结束日期
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$$anonfun$apply.apply$mcII$sp(<console>:10)
at $anonfun$$anonfun$apply.apply(<console>:10)
at $anonfun$$anonfun$apply.apply(<console>:10)
at scala.Option.map(Option.scala:146)
at $anonfun.apply(<console>:10)
at $anonfun.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$$anonfun$apply.apply(<console>:12)
at C$$anonfun$apply$$anonfun$apply.apply(<console>:12)
at scala.Option.map(Option.scala:146)
at C$$anonfun$apply.apply(<console>:12)
at C$$anonfun$apply.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$$anonfun$apply$mcVI$sp.apply$mcVI$sp(<console>:9)
at C$$anonfun$$anonfun$apply$mcVI$sp.apply(<console>:9)
at C$$anonfun$$anonfun$apply$mcVI$sp.apply(<console>:9)
at scala.Option.foreach(Option.scala:257)
at C$$anonfun.apply$mcVI$sp(<console>:9)
at C$$anonfun.apply(<console>:9)
at C$$anonfun.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))
我会这样写:
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)))
}
require
中不需要这些 if
。还要注意第二个 require
使用 "not(a) or b" is equivalent to "a => b"。最后但同样重要的是,当您检查要定义的选项时,您在对选项执行 .get
时是安全的。
这个问题我有两个解决方案。我不喜欢他们两个,所以我想知道是否有更优雅的解决方案。
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)
}
条件:
- startDate 或 endDate 都可以是 None
- 开始日期不能大于结束日期
ensuring
:
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$$anonfun$apply.apply$mcII$sp(<console>:10)
at $anonfun$$anonfun$apply.apply(<console>:10)
at $anonfun$$anonfun$apply.apply(<console>:10)
at scala.Option.map(Option.scala:146)
at $anonfun.apply(<console>:10)
at $anonfun.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$$anonfun$apply.apply(<console>:12)
at C$$anonfun$apply$$anonfun$apply.apply(<console>:12)
at scala.Option.map(Option.scala:146)
at C$$anonfun$apply.apply(<console>:12)
at C$$anonfun$apply.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$$anonfun$apply$mcVI$sp.apply$mcVI$sp(<console>:9)
at C$$anonfun$$anonfun$apply$mcVI$sp.apply(<console>:9)
at C$$anonfun$$anonfun$apply$mcVI$sp.apply(<console>:9)
at scala.Option.foreach(Option.scala:257)
at C$$anonfun.apply$mcVI$sp(<console>:9)
at C$$anonfun.apply(<console>:9)
at C$$anonfun.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))
我会这样写:
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)))
}
require
中不需要这些 if
。还要注意第二个 require
使用 "not(a) or b" is equivalent to "a => b"。最后但同样重要的是,当您检查要定义的选项时,您在对选项执行 .get
时是安全的。