使用 `Option` 字段清理 `case class`
Cleaning up `case class` with `Option` FIelds
鉴于:
case class Foo(a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int])
我只想允许构造一个 Foo
只有 如果至少有一个参数是 Some
,即不是所有字段都是 None
.
编写一个代数数据类型,然后为每个变体创建子类会是相当多的代码:
sealed trait Foo
case class HasAOnly(a: Int) extends Foo
case class HasAB(a: Int, b: Int) extends Foo
// etc...
是否有更简洁的方法,即更少的代码,可以使用 shapeless
解决我的问题?
你可以用嵌套的 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)
}
现在不可能构造一个包含所有 None
的 Foo
。您还可以将 case class 构造函数设为私有,并让 Ior
逻辑发生在伴随对象的替代构造函数中,这将使模式匹配更好一些,但它也会使示例有点更长。
不幸的是,这使用起来有点笨拙。您真正想要的是 Ior
的泛化,就像 shapeless.Coproduct
是 Either
的泛化一样。不过,我个人并不知道类似的现成版本。
我建议为您的 class 提供构建器模式。如果您的库的用户通常只指定许多可选参数中的一些,这将特别有用。作为每个参数的单独方法的奖励,他们不必将所有内容包装在 Some
中
您可以在 class 上使用单个类型参数来标记它是否完整(即至少有一个 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
多亏了 Rob Norris 最近 publicised 的 sealed abstract case class
技巧,您可以保留 Foo
案例的特征 class 而且还可以提供您自己的智能构造函数returns 一个 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))
}
}
鉴于:
case class Foo(a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int])
我只想允许构造一个 Foo
只有 如果至少有一个参数是 Some
,即不是所有字段都是 None
.
编写一个代数数据类型,然后为每个变体创建子类会是相当多的代码:
sealed trait Foo
case class HasAOnly(a: Int) extends Foo
case class HasAB(a: Int, b: Int) extends Foo
// etc...
是否有更简洁的方法,即更少的代码,可以使用 shapeless
解决我的问题?
你可以用嵌套的 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)
}
现在不可能构造一个包含所有 None
的 Foo
。您还可以将 case class 构造函数设为私有,并让 Ior
逻辑发生在伴随对象的替代构造函数中,这将使模式匹配更好一些,但它也会使示例有点更长。
不幸的是,这使用起来有点笨拙。您真正想要的是 Ior
的泛化,就像 shapeless.Coproduct
是 Either
的泛化一样。不过,我个人并不知道类似的现成版本。
我建议为您的 class 提供构建器模式。如果您的库的用户通常只指定许多可选参数中的一些,这将特别有用。作为每个参数的单独方法的奖励,他们不必将所有内容包装在 Some
您可以在 class 上使用单个类型参数来标记它是否完整(即至少有一个 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
多亏了 Rob Norris 最近 publicised 的 sealed abstract case class
技巧,您可以保留 Foo
案例的特征 class 而且还可以提供您自己的智能构造函数returns 一个 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))
}
}