Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala indexOf accepts everything

Tags:

scala

I wrote this into a REPL:

case class Thingy(s: String)
val things = List(Thingy("x"), Thingy("y"))
things.indexOf("x")

That returns -1. But I would have expected it not to compile, because "x" is a String, not a Thingy. In fact, it turns out it doesn't matter what type you put into indexOf:

things.indexOf(42)
things.indexOf(java.time.LocalDate.now())

These all return -1.

indexOf has this signature:

def indexOf[B >: A](elem: B): Int

I thought >: means B should be a supertype of A, but none of these classes are a supertype of my case class Thingy.

What am I missing here?

like image 599
jqno Avatar asked Jun 26 '15 07:06

jqno


1 Answers

B is inferred as java.io.Serializable, which is a supertype of both String and Thingy.1

This is an unfortunate consequence of two things:

  1. Scala's superfluous parent types that all/most values share (Any, Object, Serializable, et al).
  2. List being covariant.2

To define indexOf as

def indexOf(elem: A): Int

would put A in a contravariant position, which isn't allowed3 because it would violate the Liskov substitution principle: A List[Thingy] is a List[Any], and you can call .indexOf("x") on a List[Any], therefore you should be able to call .indexOf("x") on a List[Thingy].


1 If they didn't both happen to implement Serializable, it would still infer Any.

2 This is a reason to prefer invariant collections, such as scalaz.IList.

3 Try it - it won't compile: trait Foo[+A] { def bar(a: A) }

like image 188
Chris Martin Avatar answered Oct 24 '22 15:10

Chris Martin