2

What might be the most idiomatic way for turning any scala collection into an Option of the same collection, whereas when the collection is empty, the option would be None and otherwise just a Some of the original?

Oh, and without making scala create a memory copy of the entire collection for that humble conversion.

Option.apply is suited for translating null to Option of type None, a nice legacy-wrangling utility, but useless for idiomatic scala seeking the following semantics:

val some = Option(List(3,4,5))
val none = Option(List()) // seeking None, rather than Some(List()) here
matanster
  • 15,072
  • 19
  • 88
  • 167
  • 6
    I'd question the need for that. In my experience, an empty collection represents an "empty result" or "no results" just fine - no need to wrap it in an additional option. – Zoltán Oct 03 '15 at 11:30
  • 1
    @Zoltán I do not totally disagree. When however would you really reap benefit from options in your school of thought? – matanster Oct 03 '15 at 11:50
  • 1
    when you may or may not return a *single* value. The `find` method on collections is a very good example. – Zoltán Oct 03 '15 at 18:44
  • 1
    I agree with Zoltán. I can't see how wrapping a `List` in an `Option` in this manner is going to buy you anything. `Options` should be used when there is a possibility of no result at all. An empty `List` is not the same as no `List` (and as a matter of practice, if empty `Lists` are represented as `Nones`, then how to represent no `List`?) All the usual monadic operations for `Lists` (i.e. `map`, `flatMap`, `filter`, etc.) are defined on empty `Lists`, and for things like `head` which expect a non-empty `List`, we can use pattern matching, `headOption`, etc. – Jason Scott Lenderman Oct 04 '15 at 23:08
  • Thanks for commenting. Yes - of course nothing and an empty list are not the same kind of information. However in type hierarchies where both singular types and lists of types are siblings, an `Option` wrapper may sometimes make sense, for some of the methods that consume the common inherited type. – matanster Oct 05 '15 at 00:51
  • 1
    Possible duplicate of [Convert a List into an Option if it is populated](https://stackoverflow.com/questions/28244262/convert-a-list-into-an-option-if-it-is-populated) – Brian McCutchon Oct 05 '17 at 02:44
  • I disagree with @Zoltan. In many cases, I am attempting to communicate AT THE TYPE LEVEL to my API user the distinction of a non-empty collection which works great by encoding it within the `Option` type. Here is my favorite answer that I have found: https://stackoverflow.com/a/54328140/501113 – chaotic3quilibrium Jul 04 '23 at 17:23

4 Answers4

5

I'd probably just go with a filter call after creating the Option:

scala> Option(List()).filter(_.isNonEmpty)
res1: Option[List[Nothing]] = None

scala> Option(List(1,2,3)).filter(_.isNonEmpty)
res2: Option[List[Int]] = Some(List(1, 2, 3))
M. Justin
  • 14,487
  • 7
  • 91
  • 130
Shadowlands
  • 14,994
  • 4
  • 45
  • 43
4

I feel like it is not a common thing to do and can't think of any common idiomatic way, what I can suggest is adding toOption method like this:

implicit class OptList[A](val list: List[A]) extends AnyVal {
  def toOption: Option[List[A]] = if (list.isEmpty) None else Some(list)
}

and use it as follows:

scala> List().toOption
res0: Option[List[Nothing]] = None

scala> List(615, 5).toOption
res1: Option[List[Int]] = Some(List(615, 5))

toOption is a method in scala that appears in some other contexts, for example Try has toOption method.

Łukasz
  • 8,555
  • 2
  • 28
  • 51
3

Similar to Lukasz's answer, but more complete and works for any collection (I think all collections extend from Traversable as this image suggests https://i.stack.imgur.com/Dsptl.png) as requested.

object EnrichedCollections {

  implicit class CollectionOps[A <: Traversable[_]](val collection: A) extends AnyVal {

    /**
     * Returns None if the collection is empty, otherwise returns Some of the collection
     */
    def toOption: Option[A] = if (collection.isEmpty) None else Some(collection)
  }

}

import EnrichedCollections._

assert(Nil.toOption == None)
assert(Seq(1,2).toOption == Some(Seq(1,2)))
assert(Map.empty.toOption == None)
assert(Map(1 -> "hi", 2 -> "bye").toOption == Some(Map(1 -> "hi", 2 -> "bye")))
lloydmeta
  • 1,289
  • 1
  • 15
  • 25
  • you could even declare it for anthing that has isEmpty method, but anyway I see no use for converting lists to option – Łukasz Oct 03 '15 at 14:54
  • 2
    You could, but if you do it with structural typing (`A <: { def isEmpty: Boolean }`), you would be doing a reflective call to check for `isEmpty`, which has different performance characteristics than this solution. – lloydmeta Oct 03 '15 at 15:25
0

Just for fun:

val list = List(1, 2, 3, 4)
val option = list.headOption.map{_ => list}

However, I would question why you would want to do this... using isEmpty is a nice way of checking uniformly for empty lists or None values - you might find this avoids you having to convert to Option in the first place.

jazmit
  • 5,170
  • 1
  • 29
  • 36
  • Thanks. Right on, but sometimes in elaborate object relationships, an option value bears a more generalized uniform meaning than a more specific size property of a collection. Mainly when some objects sharing a common ancestry are collections and some are not. – matanster Oct 03 '15 at 11:53