Могут возникнуть проблемы, связанные с возвращением 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)