Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nil and List as case expressions in Scala

Tags:

scala

This code compiles:

def wtf(arg: Any) = {  
  arg match {  
    case Nil => "Nil was passed to arg"  
    case List() => "List() was passed to arg"  
    case _ =>"otherwise"  
  }  
}

But this one does not:

def wtf(arg: Any) = {  
  arg match {  
    case List() => "List() was passed to arg"  
    case Nil => "Nil was passed to arg"  
    case _ =>"otherwise"  
  }  
}  

The line case Nil => ... is marked as unreachable code. Why, in the first case, the line case List() => ... is not marked with the same error?

like image 441
ferrito Avatar asked Sep 25 '11 22:09

ferrito


People also ask

What is nil in Scala list?

The type of Nil is List[Nothing] and as stated above, that Nothing has no instances, we can have a List which is confirmed to be desolated. Thus, we can see that an empty list is returned. None: It is one of the children of Scala's Option class which is utilized to avoid assignment of null to the reference types.

How do you write a case condition in Scala?

Using if expressions in case statements i match { case a if 0 to 9 contains a => println("0-9 range: " + a) case b if 10 to 19 contains b => println("10-19 range: " + a) case c if 20 to 29 contains c => println("20-29 range: " + a) case _ => println("Hmmm...") }

What is case _ in Scala?

case _ => does not check for the type, so it would match anything (similar to default in Java). case _ : ByteType matches only an instance of ByteType . It is the same like case x : ByteType , just without binding the casted matched object to a name x .

What does ::: mean in Scala?

:: - adds an element at the beginning of a list and returns a list with the added element. ::: - concatenates two lists and returns the concatenated list.


2 Answers

The actual answer requires understanding an unfortunate implementation detail which cost me a lot of time to discover.

1) case List() invokes an extractor, for which no exhaustivity/unreachability checking is possible in the general case because extractors invoke arbitrary functions. So far so good, we can't be expected to overturn the halting problem.

2) Way back in the more "wild west" days of the compiler, it was determined that pattern matching could be sped up rather a lot (and not lose exhaustivity checking) if "case List()" were just translated to "case Nil" during an early compiler phase so it would avoid the extractor. This is still the case and although it could be undone, apparently lots of people have been told that "case List() => " is perfectly fine and we don't want to pessimize all their code all of a sudden. So I just have to figure out a road out.

You can see empirically that List is privileged by trying it with some other class. No unreachability errors.

import scala.collection.immutable.IndexedSeq
val Empty: IndexedSeq[Nothing] = IndexedSeq()
def wtf1(arg: Any) = {  
  arg match {  
    case Empty => "Nil was passed to arg"  
    case IndexedSeq() => "IndexedSeq() was passed to arg"  
    case _ =>"otherwise"  
  }  
}

def wtf2(arg: Any) = {  
  arg match {  
    case IndexedSeq() => "IndexedSeq() was passed to arg"  
    case Empty => "Nil was passed to arg"  
    case _ =>"otherwise"  
  }  
}  
like image 135
psp Avatar answered Nov 05 '22 20:11

psp


The discrepancy is particularly weird since the code for the Nil case in the second version is definitely not unreachable, as we can see if we hide things from the compiler a bit:

def wtf(arg: Any) = {
  arg match {
    case List() => "List() was passed to arg"  
    case x => x match {
      case Nil => "Nil was passed to arg"
      case _ =>"otherwise"
    }
  }
}

Now wtf(Vector()) will return "Nil was passed to arg". This may also seem counterintuitive, but it's because literal patterns match values that are equal in terms of ==, and Vector() == Nil, but Vector() doesn't match the extractor pattern List().

More concisely:

scala> (Vector(): Seq[_]) match { case List() => true; case Nil => false }
<console>:8: error: unreachable code

scala> (Vector(): Seq[_]) match { case List() => true; case x => x match { case Nil => false } }
res0: Boolean = false

So the compiler's response is completely reversed: in the "good" version the second case is unreachable, and in the "bad" version the second case is perfectly fine. I've reported this as a bug (SI-5029).

like image 21
Travis Brown Avatar answered Nov 05 '22 20:11

Travis Brown