Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: who can explain this?

Tags:

generics

scala

Consider the following Scala code:

case class Data[T](value: Option[T]) {
  def get: T = try {
    doGet
  } catch {
    case e: Exception => throw new IllegalArgumentException
  }

  def doGet: T = value match {
    case Some(v) => v
    case None => ().asInstanceOf[T]
  }
}

Data[Unit](None).get
Data[Integer](None).get // which exception is thrown here?

[spoiler] It is a ClassCastException; who can explain why it is not caught and replaced by an IllegalArgumentException?

PS: To preempt any questions on why I would want to do this: this is a simplified version of some code that uses json4s to parse some string into an Option[T]; if the parsing fails None is returned, which is OK if T was Unit and not OK if T is some other type.

like image 301
Hugo Zwaal Avatar asked Sep 12 '13 19:09

Hugo Zwaal


People also ask

What does => mean in Scala?

=> is the "function arrow". It is used both in function type signatures as well as anonymous function terms. () => Unit is a shorthand for Function0[Unit] , which is the type of functions which take no arguments and return nothing useful (like void in other languages).

What is classOf in Scala?

A classOf[T] is a value of type Class[T] . In other words, classOf[T]: Class[T] . For example: scala> val strClass = classOf[String] strClass: Class[String] = class java. lang. String scala> :t strClass Class[String]

What is any data type in Scala?

Any has two direct subclasses: AnyVal and AnyRef . AnyVal represents value types. There are nine predefined value types and they are non-nullable: Double , Float , Long , Int , Short , Byte , Char , Unit , and Boolean . Unit is a value type which carries no meaningful information.

Is everything an object in Scala?

Everything is an Object. Scala is a pure object-oriented language in the sense that everything is an object, including numbers or functions. It differs from Java in that respect, since Java distinguishes primitive types (such as boolean and int ) from reference types.


1 Answers

Explanation

Exception isn't thrown here:

().asInstanceOf[T]

because this is an unchecked cast - JVM cannot verify if it is possible to cast () into T, because it has no information about T due to type erasure.

Instead, exception is thrown here

Data[Integer](None).get

because the result of get is cast into an Integer and that is something that JVM can verify. So, ClassCastException is actually thrown outside of get.

BTW, javac always warns about unchecked casts, I don't know why scalac doesn't.

Workaround

To some extent, it is possible to work around type erasure here using ClassTag and reflection-based casting:

import scala.reflect.{ClassTag, classTag}

case class Data[T: ClassTag](value: Option[T]) {
  def get: T = try {
    doGet
  } catch {
    case e: Exception => throw new IllegalArgumentException
  }

  def doGet: T = value match {
    case Some(v) => v
    case None => classTag[T].runtimeClass.asInstanceOf[Class[T]].cast(())
  }
}

Hackaround

For this use case, you can inspect the ClassTag directly:

scala> case class Data[T](value: Option[T])(implicit t: ClassTag[T]) {
     | def get: T = value getOrElse (t match {
     |   case ClassTag.Unit => ().asInstanceOf[T]
     |   case _ => throw new IllegalArgumentException
     | })
     | }
defined class Data

scala> Data[Unit](None)
res6: Data[Unit] = Data(None)

scala> .get

scala> Data[Int](None).get
java.lang.IllegalArgumentException
like image 181
ghik Avatar answered Oct 12 '22 16:10

ghik