Складывание на тематических классах - PullRequest
3 голосов
/ 26 ноября 2010

У меня есть ситуация, когда у меня есть пара классов case, где все их переменные являются необязательными.

Допустим, у меня есть:

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

Учитывая коллекцию класса case того же типа, я хотел бы сложить их, сравнивая значения параметров и сохраняя значения, которые определены. То есть для Size:

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

Я хотел бы сделать это в более общем виде для любого из классов case, подобных приведенным выше. Я думаю о том, чтобы сделать что-то с unapply(_).get и т. Д. Кто-нибудь знает умный способ решить эту проблему?

Ответы [ 3 ]

2 голосов
/ 26 ноября 2010

Хорошо, учтите это:

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 для каждой арности. Затем он может быть использован с любым классом case. Поскольку здесь есть о чем беспокоиться, ниже приведен один из способов заставить его работать с классами case или двумя параметрами. Расширение его до большего количества параметров является тривиальным, хотя и немного утомительным.

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)
    }
  }
}

Однако, когда вы сделаете это, вам придется явно превратить apply и unapply в функции:

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 мне сказали, что ответ окончательный - нет, по крайней мере на то, как я подошел к проблеме.

2 голосов
/ 26 ноября 2010

[Код обновлен]

Вот решение, которое требует только одного абстрактного класса на «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))

Обратите внимание, что неявный аргумент <:< выдаст ошибку времени компиляции, когда другие классы дел с такими же аргументами конструктора будут переданы методу.

Однако требуется «правильно сформированный» конструктор, иначе код отражения взорвется.

1 голос
/ 26 ноября 2010

Вы можете использовать productElement или productIterator (на scala.Product ) для общего извлечения / итерации элементов классов дел (и кортежей), но они имеют тип Any, поэтому будет немного боли.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...