Подтвердить что ты не робот

Почему опция недоступна?

Существует ли какое-либо рациональное для 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, другое коммит

4b9b3361

Ответ 1

Могут возникнуть проблемы с тем, что 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 extend TraversableLike[A, OptionX[A]] и переопределяя newBuilder.

Но тогда я получаю ошибки времени выполнения на SomeX(1) ++ SomeX(2) или for (i <- SomeX(1); j <- List(1,2)) yield (i+j). Поэтому я не думаю, что это возможно, чтобы расширение 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)

Ответ 2

Это не 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

Какое значение этого?

Ответ 3

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

Ответ 4

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

Говорят, что дизайн закончен, когда нет ничего, что можно было добавить, но ничего не оставалось убрать. Это, конечно, не означает, что стандартная библиотека Scala идеальна.