Search code examples
scalaoption-typescala-option

Asymmetry of (Some (x), None).min and max


Accidentally, I observed an asymmetry.

Let's have a list:

val li = List (Some (3), Some (2), None, Some (9)) 
li: List[Option[Int]] = List(Some(3), Some(2), None, Some(9))

scala> li.max
res54: Option[Int] = Some(9)

Ok - Some(9) is bigger than None. Why? A convention? Is None converted to Null and Null autoboxed into a 0?

scala> li.min
res55: Option[Int] = None

Looks, as if that impression is true, but let's introduce a negative number:

scala> val li = List (Some (3), Some (-2), None, Some (9)) 
li: List[Option[Int]] = List(Some(3), Some(-2), None, Some(9))

scala> li.min
res52: Option[Int] = None

scala> li.max
res53: Option[Int] = Some(9)

That's surprising, at least for me.

Yes, I know, I'm doing it wrong. The right way to go is, to flatten first, and everything is fine:

scala> li.flatten.min
res57: Int = -2

scala> val li = List (Some (3), Some (2), None, Some (9)) 
li: List[Option[Int]] = List(Some(3), Some(2), None, Some(9))

scala> li.flatten.min
res56: Int = 2

But my question is still open:

Are None and Some (x), lacking other comparabilities, regarded as Objects, and then by toString compared ('N' < 'S')?


Solution

  • Have a look at the source:

      trait OptionOrdering[T] extends Ordering[Option[T]] {
        def optionOrdering: Ordering[T]
        def compare(x: Option[T], y: Option[T]) = (x, y) match {
          case (None, None)       => 0
          case (None, _)          => -1
          case (_, None)          => 1
          case (Some(x), Some(y)) => optionOrdering.compare(x, y)
        }
      }
      implicit def Option[T](implicit ord: Ordering[T]): Ordering[Option[T]] =
        new OptionOrdering[T] { val optionOrdering = ord }
    

    Some(x) is always treated as greater than None. There is no attempt to treat None as zero or anything else. It will therefore always be the minimum of a collection of Option[A], for any A (assuming said collection contains None). There's no asymmetry in that, it's just the convention. I don't think it would really make sense to try to convert None to an arbitrary number for the comparison, so having None be less than every Some(x) makes sense. Otherwise, you'd need a special Ordering[Option[A]] for every Ordering[A] to handle those special cases, instead of the above generic code.