使用`Option` FIelds

时间:2016-10-20 18:19:54

标签: scala shapeless

假设:

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... 解决我的问题是否有更清晰,即代码更少的方法?

3 个答案:

答案 0 :(得分:6)

You can do something like this with nested Iors:

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 Nones. 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最近publicisedsealed 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