Почему опция не прослеживается? - PullRequest
14 голосов
/ 04 января 2012

Есть ли какое-то разумное объяснение, что Option не является Traversable?

В Scala 2.9, Seq(Set(1,3,2),Seq(4),Option(5)).flatten не компилируется, а просто использует его для реализации швов черты Traversable, рациональных для меня. Если это не так, должно быть что-то, чего я не вижу, что не позволяет этого. Что это?

PS: пытаясь понять, я достиг ужасных вещей, которые компилируются, например:

scala> Seq(Set(1,3,2),Seq(4),Map("one"->1, 2->"two")).flatten
res1: Seq[Any] = List(1, 3, 2, 4, (one,1), (2,two))

PS2: я знаю, что могу написать: Seq(Set(1,3,2),Seq(4),Option(5).toSeq).flatten или другую некрасивую вещь.

PS3: В течение последнего месяца будут работать швы, чтобы Option выглядел больше как Traversable без его реализации: commit , еще один коммит

Ответы [ 4 ]

5 голосов
/ 04 января 2012

Могут возникнуть проблемы, связанные с возвращением flatMap Option, а не Traversable. Хотя это предшествует всему механизму 2.8 CanBuildFrom.

Вопрос был задан один раз в списке рассылки, но ответа не было.

Вот иллюстрация:

sealed trait OptionX[+A] extends Traversable[A] {
  def foreach[U](f: (A) => U): Unit = if (!isEmpty) f(get)
  def get: A
  def isDefined: Boolean
  def getOrElse[B >: A](default: => B): B
}

case class SomeX[+A](a: A) extends OptionX[A] {
  override def isEmpty = false
  def get = a
  def isDefined = true
  def getOrElse[B >: A](default: => B) = a
}

case object NoneX extends OptionX[Nothing] {
  override def isEmpty = true
  def get = sys.error("none")
  def isDefined = false
  def getOrElse[B](default: => B) = default
}

object O extends App {
  val s: OptionX[Int] = SomeX(1)
  val n: OptionX[Int] = NoneX
  s.foreach(i => println("some " + i))
  n.foreach(i => println("should not print " + i))
  println(s.map(_ + "!"))
}

Последняя строка возвращает List("1!") вместо Option. Может быть, кто-то может придумать CanBuildFrom, который даст SomeX("1!"). Моя попытка не удалась:

object OptionX {
  implicit def canBuildFrom[Elem] = new CanBuildFrom[Traversable[_], Elem, OptionX[Elem]] {
    def builder() = new Builder[Elem, OptionX[Elem]] {
      var current: OptionX[Elem] = NoneX
      def +=(elem: Elem): this.type = {
        if (current.isDefined) sys.error("already defined")
        else current = SomeX(elem)
        this
      }
      def clear() { current = NoneX }
      def result(): OptionX[Elem] = current
    }
    def apply() = builder()
    def apply(from: Traversable[_]) = builder()
  }
}

Мне нужно явно передать неявное:

scala> import o._
import o._

scala> val s: OptionX[Int] = SomeX(1)
s: o.OptionX[Int] = SomeX(1)

scala> s.map(_+1)(OptionX.canBuildFrom[Int])
res1: o.OptionX[Int] = SomeX(2)

scala> s.map(_+1)
res2: Traversable[Int] = List(2)

Edit:

Итак, я смог обойти эту проблему и SomeX(1).map(1+) вернуть OptionX с помощью OptionX extension TraversableLike[A, OptionX[A]] и переопределением newBuilder.

Но тогда я получаю ошибки времени выполнения на SomeX(1) ++ SomeX(2) или for (i <- SomeX(1); j <- List(1,2)) yield (i+j). Так что я не думаю, что возможно иметь опцию extension Traversable и делать что-то вменяемое с точки зрения возврата наиболее определенного типа.

Помимо осуществимости, с точки зрения стиля кодирования, я не уверен, что хорошо, когда Option ведет себя как Traversable при любых обстоятельствах. Option представляют значения, которые не всегда определены, в то время как Traversable определяет методы для коллекций, которые могут иметь несколько элементов, таких как drop(n), splitAt(n), take(n), ++. Хотя было бы удобно, если бы Option было также Traversable, я думаю, что это может сделать намерение менее ясным.

Использование toSeq там, где это необходимо, кажется безболезненным способом указать, что я хочу, чтобы мой вариант вел себя как Traversable. А для некоторых повторяющихся сценариев использования существует неявное преобразование option2Iterable - например, это уже работает (все они возвращают List(1,2)):

  • List(Option(1), Option(2), None).flatten
  • for (i <- List(0,1); j <- Some(1)) yield (i+j)
  • Some(1) ++ Some(2)
4 голосов
/ 05 января 2012

Это не Traversable, потому что вы не можете реализовать scala.collection.mutable.Builder для него.

Ну, это может быть Traversable, но это приведет к множеству методов, которые возвращают Option, теперь возвращая Traversable.Если вы хотите узнать, что это за методы, просто посмотрите на методы, которые принимают параметр CanBuildFrom.

Давайте рассмотрим пример кода, чтобы продемонстрировать, почему:

Seq(Set(1,3,2),Seq(4),Option(5)).flatten

Это должнобыть равным:

Seq(1, 2, 3, 4, 5)

Теперь давайте рассмотрим эту альтернативу:

Option(Set(1,3,2),Seq(4),Option(5)).flatten

Какова ценность этого?

1 голос
/ 05 января 2012

Возможно, я плотный, но я не понимаю, зачем кому-то это понадобится.Более того, None также должно быть Traversable, что кажется семантически сомнительным.

Говорят, что дизайн закончен не тогда, когда нечего добавить, а, скорее, ничего не отнять.Конечно, нельзя сказать, что стандартная библиотека Scala идеальна.

1 голос
/ 04 января 2012

Причина в том, что в некоторых случаях, когда применяются импликации, тип будет менее точным.У вас все равно будет значение Option, но статический тип возврата будет примерно таким же, как Iterable, например, не самый точный.

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