Reputation: 1594
I want to write a method, which returns None
if a collection is empty and Some(collection)
in other case.
Best I can get is
implicit class CollectionExtensions[A, Repr](self: TraversableLike[A, Repr]){
def toOption: Option[Repr] = if (self.nonEmpty) Some(self.asInstanceOf[Repr]) else None
}
But casting .asInstanceOf[Repr]
seems wrong. What is the correct way?
Upvotes: 3
Views: 799
Reputation: 22374
In order to recover original Repr
type, you can use self.repr
(scaladoc)
implicit class CollectionExtensions[A, Repr](self: TraversableLike[A, Repr]){
def toOption: Option[Repr] = if (self.nonEmpty) Some(self.repr) else None
}
If you would just stick with Option[TraversableLike[A, Repr]]
as @chengpohi's answer suggests, operations like map
on it (list.toOption.map(_.map(x => x))
) would return you Option[Traversable[T]]
loosing the original Repr
type (like List[Int]
). repr
helps with that:
def repr: Repr
The collection of type traversable collection underlying this TraversableLike object. By default this is implemented as the TraversableLike object itself, but this can be overridden.
However, the funny thing is, if you look at repr
's code (here):
def repr: Repr = this.asInstanceOf[Repr]
It does same thing, but at least it wrapped (hidden?) nicely in scala-library and more abstract, so by using it you could account for potential redefinitions.
Also, it worth mentioning that this non-empty-collection approach is popular in scalaz/cats:
scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._
scala> List(1, 2, 3).toNel
res8: Option[scalaz.NonEmptyList[Int]] = Some(NonEmptyList(1, 2, 3))
scala> nil[Int].toNel
res9: Option[scalaz.NonEmptyList[Int]] = None
toNel
here means toNonEmptyList
, so it's not as abstract as your solution. Cats have OneAnd[A,Repr]
and some helpful implicits. See http://typelevel.org/cats/datatypes/oneand.html
Upvotes: 4
Reputation: 14224
Here are some other approaches you can use in addition to @dk14 answer:
Use an implicit class for Repr with TraversableOnce[A]
. This will also support Iterator
, because Iterator
extends TraversableOnce
, but not TraversableLike
.
implicit class CollectionExtensions[A, Repr](val self: Repr with TraversableOnce[A]) extends AnyVal {
def toOption: Option[Repr] = if (self.nonEmpty) Some(self) else None
}
Use an implicit class just for Repr
, but request evidence that it's implicitly convertible to Traversable
. This approach also supports Array
s and String
s, because they don't extend Traversable
at all, but are implicitly convertible to it.
implicit class CollectionExtensions[Repr](val self: Repr) extends AnyVal {
def toOption[A](implicit ev: Repr => TraversableOnce[A]): Option[Repr] = {
val traversable = ev(self)
if (traversable.isEmpty) None else Some(self)
}
}
Both of those approaches preserve the original type:
scala> List(1, 2, 3).toOption
res1: Option[List[Int]] = Some(List(1, 2, 3))
scala> Iterator(1, 2, 3).toOption
res2: Option[Iterator[Int]] = Some(non-empty iterator)
scala> Array.empty[Int].toOption
res3: Option[Array[Int]] = None
scala> Map(1 -> 2).toOption
res4: Option[scala.collection.immutable.Map[Int,Int]] = Some(Map(1 -> 2))
scala> "123".toOption
res5: Option[String] = Some(123)
scala> "".toOption
res6: Option[String] = None
Upvotes: 6