Ramy
Ramy

Reputation: 21261

why does this compile?

Why does this compile:

scala> def last[A](a : List[A] ) : A =     
 | a match {                           
 |   case head :: Nil => Some(head) get
 |   case _ :: tail => last(tail)      
 |   case Nil => None get              
 | }                   

last: [A](a: List[A])A

While this obviously doesn't:

scala> None get

java.util.NoSuchElementException: None.get
at scala.None$.get(Option.scala:262)
at .<init>(<console>:6)
at .<clinit>(<console>)
at RequestResult$.<init>(<console>:9)
at RequestResult$.<clinit>(<console>)
at RequestResult$scala_repl_result(<console>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$17.apply(Interpreter.scala:988)
at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$17.apply(Interpreter.scala:988)
at scala.util.control.Exception$C...

Upvotes: 1

Views: 248

Answers (1)

Blaisorblade
Blaisorblade

Reputation: 6488

The latter code does compile and fails just at runtime - what you see is an exception, not a compiler error. None inherits from Option and thus must define the method get.

EDIT: The question could be rephrased as "why does the compiler accept a clearly stupid call to None.get?". This question is indeed nontrivial to answer, so I'll spend a few more words on it at different levels.

  • First a clarification: to understand the difference between compile-time and run-time errors, it is probably instructive to create a Scala program containing the code you want to test, compile it and test it. If you run code only at the prompt, it becomes harder for a beginner to appreciate the difference between a compile-time error and a run-time error. Still, the savvy user will learn to recognize the different format of the errors.
  • Isn't a method like get stupid, since it can fail at runtime? Indeed, when you have a value v of type Option[T], you should usually pattern match on it or use methods like getOrElse, map, and so on: methods which work even if v is empty. But sometimes you have such a value and are already sure that v must be Some(q): get should only be used then.
  • Why, given the definition of None, cannot the compiler give a compile-time error? The compiler gives an error when you call a method which is not defined on the receiver object, but get is defined on the Option class, and therefore on its two subclasses, Some and None. It is only its body that contains a call to error.
  • Why does the given definitions of Option make sense? For the user to be call to call get when he wants to, it must be defined on Option. Otherwise one would need a typecast before being able to call it - at that point, it'd be easier to just do pattern matching.
  • Why doesn't still the compiler recognize that the code is going to fail at runtime? Because recognizing the nontrivial cases is hard. For instance, in the function you posted above, it is not clear that None.get is ever going to be executed; moreover, the code you gave will give a run-time error if you pass in an empty list; therefore it is a correct implementation of the usual specification of last. In general, you might have v get and it might be nontrivial to understand whether v might or might not be None. There are other tools which try to find this kind of bugs (not sure if any for Scala yet), but they also tend to give either false negatives or false positives: they might report problems which cannot happen, or miss relevant problems. In the end, you really don't want their output to be together the output of the Scala compiler. Having some basic easy checks within the compiler might make sense, but there you have an engineering question: it's not clear if the extra code is worth its cost.

Upvotes: 12

Related Questions