5

The question isn't about how to test an array for emptiness (arr.length == 0 does this fine). Rather my question is, why does

scala> Array().isEmpty
res1: Boolean = true

work and

scala> val x = Array[String]()
x: Array[String] = Array()
scala> x.isEmpty
res2: Boolean = true

work, but

scala> val y = Array()
y: Array[Nothing] = Array()

scala> y.isEmpty
<console>:13: error: value isEmpty is not a member of Array[Nothing]
       y.isEmpty
         ^

does not?

3
  • 3
    See stackoverflow.com/questions/5843001/… Commented Jan 31, 2017 at 17:38
  • Funny, Array().isEmpty works :) Commented Jan 31, 2017 at 17:39
  • Seems as though Array().isEmpty works because Array() hasn't yet been inferred as empty, so it picks up Predef.refArrayOps. Commented Jan 31, 2017 at 17:51

2 Answers 2

4

As @MichaelZajac points out, Nothing is a subtype of everything (its counterpart Any is a supertype of everything). In particular it's also a subtype of AnyRef. In fact there's an even more general genericArrayOps which has no type bound at all (e.g. Array[Any]().isEmpty works)! The implicit conversion allowing you to use isEmpty should kick in, but of course it doesn't, even though explicitly calling the conversion is fine.

The link @slouc gives is the answer, namely that the Scala compiler treats Nothing in a special way when doing implicit resolution because Nothing is the default lower bound for a type when doing type inference.

Now why exactly would it ever be desirable for Nothing not to be considered in implicit resolution? Well the tricky thing about Nothing again is that it's a subtype of everything. This means that if at any point Scala infers a type to be Nothing, every single implicit conversion would immediately become valid. This could hide a type error (you should never have an instance of Nothing, but when that Nothing becomes an Int... well who's to say?). (Note I would love for someone who actually hacks on the compiler to jump in and confirm/deny/elaborate on this)

Sign up to request clarification or add additional context in comments.

Comments

3

EDIT: probably this answer is not correct. But I'm keepeing it here to show how did I try to investigate this issue. To me it looks like a bug in the compiler.

The answer is implicit conversion that exists for Array[T <: AnyRef] String is AnyRef, Nothing is not AnyRef.

How could you discover this yourself?

In IntelliJ you can see a grey underline under the isEmpty enter image description here

This means that the method isEmpty is not a method of Array, but an implicit (a method on a class that has implicit conversion from Array).

Now, to discover the implicit conversion, just click ctrl+q enter image description here

So when you follow it, you get to this line in the source code -

  implicit def refArrayOps[T <: AnyRef](xs: Array[T]): ArrayOps[T]    = new ArrayOps.ofRef[T](xs)

That explains that the implicit conversion is on an Array[T] when T extends AnyRef

So back to our case - String <: AnyRef, but this doesn't hold for Nothing

3 Comments

Makes a lot of sense, thanks so much for the explanation! I'm not the biggest fan of IntelliJ, but that's a cool feature.
This reasoning here isn't true. Nothing is absolutely a sub-type of AnyRef. Try: refArrayOps[Nothing](Array()).isEmpty -- it works just fine. The reason why the implicit doesn't kick-in for y.isEmpty is far more dubious than that.
@MichaelZajac What do you mean by "far more dubious"?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.