折叠案例类别

发布于 2024-10-04 11:01:34 字数 485 浏览 1 评论 0原文

我遇到的情况是,我有几个案例类,其中所有变量都是可选的。

假设我有:

case class Size(width: Option[Int], height: Option[Int])
case class Foo(a: Option[String], b: Option[Boolean], c: Option[Char])

给定相同类型案例类的集合,我想将它们折叠起来比较选项值并保留定义的值。即对于 Size

values.foldLeft(x) { (a, b) =>
  Size(a.width.orElse(b.width), a.height.orElse(b.height))
}

我想以更通用的方式对任何像上面这样的案例类执行此操作。我正在考虑用 unapply(_).get 等做一些事情。有谁知道解决这个问题的聪明方法吗?

I have a situation where I have a couple of case classes where all of their variables are optional.

Let's say I have:

case class Size(width: Option[Int], height: Option[Int])
case class Foo(a: Option[String], b: Option[Boolean], c: Option[Char])

Given a collection of the same type of case class I would like to fold over them comparing the option values and keep the values which are defined. I.e. for Size:

values.foldLeft(x) { (a, b) =>
  Size(a.width.orElse(b.width), a.height.orElse(b.height))
}

I would like to do this in a more general way for any of the case classes like the ones above. I'm thinking about doing something with unapply(_).get etc. Does anyone know a smart way to solve this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

柒七 2024-10-11 11:01:34

好的,考虑一下:

def foldCase[C,T1](unapply: C => Option[Option[T1]], apply: Option[T1] => C)
  (coll: Seq[C]): C = {
  coll.tail.foldLeft(coll.head) { case (current, next) =>
    apply(unapply(current).get orElse unapply(next).get)
  }
}

case class Person(name: Option[String])

foldCase(Person.unapply, Person.apply)(List(Person(None), Person(Some("Joe")), Person(Some("Mary"))))

可以重载 foldCase 以接受两个、三个或更多参数,每个参数都有一个版本的 f。然后它可以与任何案例类别一起使用。由于需要担心元组问题,因此下面是使其与案例类或两个参数一起使用的一种方法。将其扩展到更多参数就很简单了,尽管有点烦人。

def foldCase[C,T1,T2](unapply: C => Option[(Option[T1], Option[T2])], apply: (Option[T1], Option[T2]) => C)
  (coll: Seq[C]): C = {
  def thisOrElse(current: (Option[T1], Option[T2]), next: (Option[T1], Option[T2])) =
    apply(current._1 orElse next._1, current._2 orElse next._2)
  coll.tail.foldLeft(coll.head) { case (current, next) =>
    thisOrElse(unapply(current).get, unapply(next).get)
  }
}

val list = Person(None, None) :: Person(Some("Joe"), None) :: Person(None, Some(20)) :: Person(Some("Mary"), Some(25)) :: Nil

def foldPerson = foldCase(Person.unapply, Person.apply) _

foldPerson(list)

要重载使用它,只需将所有定义放入一个对象中:

object Folder {
  def foldCase[C,T1](unapply: C => Option[Option[T1]], apply: Option[T1] => C)
    (coll: Seq[C]): C = {
    coll.tail.foldLeft(coll.head) { case (current, next) =>
      apply(unapply(current).get orElse unapply(next).get)
    }
  }

  def foldCase[C,T1,T2](unapply: C => Option[(Option[T1], Option[T2])], apply: (Option[T1], Option[T2]) => C)
    (coll: Seq[C]): C = {
    def thisOrElse(current: (Option[T1], Option[T2]), next: (Option[T1], Option[T2])) =
      apply(current._1 orElse next._1, current._2 orElse next._2)
    coll.tail.foldLeft(coll.head) { case (current, next) =>
      thisOrElse(unapply(current).get, unapply(next).get)
    }
  }
}

但是,当您这样做时,您必须显式地将 applyunapply 转换为函数:

case class Question(answer: Option[Boolean])
val list2 = List(Question(None), Question(Some(true)), Question(Some(false)))
Folder.foldCase(Question.unapply _, Question.apply _)(list2)

它可能是可以将它变成结构类型,这样你只需要传递伴生对象,但我做不到。在#scala 上,我被告知答案是肯定的,至少对于我处理这个问题的方式来说是这样。

Ok, consider this:

def foldCase[C,T1](unapply: C => Option[Option[T1]], apply: Option[T1] => C)
  (coll: Seq[C]): C = {
  coll.tail.foldLeft(coll.head) { case (current, next) =>
    apply(unapply(current).get orElse unapply(next).get)
  }
}

case class Person(name: Option[String])

foldCase(Person.unapply, Person.apply)(List(Person(None), Person(Some("Joe")), Person(Some("Mary"))))

One could overload foldCase to accept two, three, or more parameters, one version of f for each arity. It could then be used with any case class. Since there's the tuple-thing to worry about, below's one way to make it work with case classes or two parameters. Expanding it to more parameters is then trivial, though a bit tiresome.

def foldCase[C,T1,T2](unapply: C => Option[(Option[T1], Option[T2])], apply: (Option[T1], Option[T2]) => C)
  (coll: Seq[C]): C = {
  def thisOrElse(current: (Option[T1], Option[T2]), next: (Option[T1], Option[T2])) =
    apply(current._1 orElse next._1, current._2 orElse next._2)
  coll.tail.foldLeft(coll.head) { case (current, next) =>
    thisOrElse(unapply(current).get, unapply(next).get)
  }
}

val list = Person(None, None) :: Person(Some("Joe"), None) :: Person(None, Some(20)) :: Person(Some("Mary"), Some(25)) :: Nil

def foldPerson = foldCase(Person.unapply, Person.apply) _

foldPerson(list)

To use it overloaded, just put all definitions inside one object:

object Folder {
  def foldCase[C,T1](unapply: C => Option[Option[T1]], apply: Option[T1] => C)
    (coll: Seq[C]): C = {
    coll.tail.foldLeft(coll.head) { case (current, next) =>
      apply(unapply(current).get orElse unapply(next).get)
    }
  }

  def foldCase[C,T1,T2](unapply: C => Option[(Option[T1], Option[T2])], apply: (Option[T1], Option[T2]) => C)
    (coll: Seq[C]): C = {
    def thisOrElse(current: (Option[T1], Option[T2]), next: (Option[T1], Option[T2])) =
      apply(current._1 orElse next._1, current._2 orElse next._2)
    coll.tail.foldLeft(coll.head) { case (current, next) =>
      thisOrElse(unapply(current).get, unapply(next).get)
    }
  }
}

When you do this, however, you'll have to explicitly turn apply and unapply into functions:

case class Question(answer: Option[Boolean])
val list2 = List(Question(None), Question(Some(true)), Question(Some(false)))
Folder.foldCase(Question.unapply _, Question.apply _)(list2)

It might be possible to turn it into a structural type, so that you only need to pass the companion object, but I couldn't do it. On #scala, I was told the answer is a definitive no, at least to how I approached the problem.

蒲公英的约定 2024-10-11 11:01:34

[代码更新]

这是一种解决方案,每个“arity”只需要一个抽象类:

abstract class Foldable2[A,B](val a:Option[A], val b:Option[B]) {
  def orElse[F <: Foldable2[A,B]](that: F)(implicit ev: this.type <:< F) = 
    getClass.getConstructor(classOf[Option[A]], classOf[Option[B]]).newInstance(
      this.a.orElse(that.a), this.b.orElse(that.b)
    )
}

case class Size(w: Option[Int], h: Option[Int]) extends Foldable2(w, h) 

println(Size(Some(1),None).orElse(Size(Some(2),Some(42))))
//--> Size(Some(1),Some(42))

请注意,当其他具有相同构造函数的案例类时,隐式 <:< 参数将给出编译时错误参数被传递给该方法。

然而,需要一个“格式良好”的构造函数,否则反射代码将会崩溃。

[Code updated]

Here is an solution which requires only one abstract class per "arity":

abstract class Foldable2[A,B](val a:Option[A], val b:Option[B]) {
  def orElse[F <: Foldable2[A,B]](that: F)(implicit ev: this.type <:< F) = 
    getClass.getConstructor(classOf[Option[A]], classOf[Option[B]]).newInstance(
      this.a.orElse(that.a), this.b.orElse(that.b)
    )
}

case class Size(w: Option[Int], h: Option[Int]) extends Foldable2(w, h) 

println(Size(Some(1),None).orElse(Size(Some(2),Some(42))))
//--> Size(Some(1),Some(42))

Note that the implicit <:< argument will give a compile time error when other case classes with the same constructor arguments are passed to the method.

However, a "well formed" constructor is required, else the reflection code will blow up.

审判长 2024-10-11 11:01:34

您可以使用 productElementproductIterator (在 scala.Product) 来一般检索/迭代案例类(和元组)的元素,但它们的类型为 Any,所以会有一些痛苦。

You can use productElement or productIterator (on scala.Product) to generically retrieve/iterate the elements of case classes (and tuples), but they're typed as Any, so there will be some pain.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文