假设:
case class Foo(a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int])
如果至少有一个参数是Foo
,我希望只允许构建Some
,即并非所有字段都是{{1} }。
编写代数数据类型,然后为每个变体创建子类将是相当多的代码:
None
使用sealed trait Foo
case class HasAOnly(a: Int) extends Foo
case class HasAB(a: Int, b: Int) extends Foo
// etc...
解决我的问题是否有更清晰,即代码更少的方法?
答案 0 :(得分:6)
You can do something like this with nested Ior
s:
import cats.data.Ior
case class Foo(iors: Ior[Ior[Int, Int], Ior[Int, Int]]) {
def a: Option[Int] = iors.left.flatMap(_.left)
def b: Option[Int] = iors.left.flatMap(_.right)
def c: Option[Int] = iors.right.flatMap(_.left)
def d: Option[Int] = iors.right.flatMap(_.right)
}
Now it's impossible to construct a Foo
with all None
s. You could also make the case class constructor private and have the Ior
logic happen in an alternative constructor on the companion object, which would make pattern matching a little nicer, but it would also make the example a little longer.
Unfortunately this is kind of clunky to use. What you really want is a generalization of Ior
in the same way that shapeless.Coproduct
is a generalization of Either
. I'm not personally aware of a ready-made version of anything like that, though.
答案 1 :(得分:5)
感谢Rob Norris最近publicised的sealed abstract case class
技巧,您可以保留Foo
案例类的特征,同时提供自己的智能构造函数,返回Option[Foo]
取决于给定的参数是否通过了所有标准:
sealed abstract case class Foo(
a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int])
object Foo {
private class Impl(
a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int])
extends Foo(a, b, c, d)
def apply(
a: Option[Int],
b: Option[Int],
c: Option[Int],
d: Option[Int]): Option[Foo] =
(a, b, c, d) match {
case (None, None, None, None) => None
case _ => Some(new Impl(a, b, c, d))
}
}
答案 2 :(得分:0)
我建议为您的班级提供建设者模式。如果库的用户通常只指定一些可选参数,则此功能尤其有用。作为每个参数单独方法的奖励,他们不必将所有内容包装在Some
您可以在类上使用单个类型参数来标记它是否完整(即至少有一个Some
参数),并且您可以使用隐式参数对构建方法强制执行此操作。
sealed trait Marker
trait Ok extends Marker
trait Nope extends Markee
case class Foo private(a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int])
object Foo{
case class Builder[T <: Marker](foo: Foo){
def a(x:Int) = Builder[Ok](foo = foo.copy(a=Some(x)))
def b(x:Int) = Builder[Ok](foo = foo.copy(b=Some(x)))
// ...
def build(implicit ev: T <:< Ok) = foo
}
def create = Builder[Nope](Foo(None, None, None, None))
}
我之前尝试过类型安全的构建器。这个要点有一个更复杂的例子,虽然它也跟踪已经设置了哪个字段,以便以后可以提取它而不会对Option.get
进行不安全的调用。
https://gist.github.com/gjuhasz86/70cb1ca2cc057dac5ba7