posted by codders in
scala
So here’s one that’s got me thinking. I think this is a fairly straightforward use case, but maybe I’m doing something odd. I have an application where I’m putting objects in to a hash and I want to be able to pull them out with the “correct” types. Which is to say I want to be able not to have to cast the objects on their way out but rather have their types inferred by their use context.
The following code compiles. There’s a method where I’m putting the type hints in, a method where I’m not, and a method where I’m passing the retrieved hash value directly to an explicitly typed function. In all three cases, at least in principle, the runtime types should match – I’m putting in a String, I expect a String out the other side.
There is, so we’re clear, a runtime cast (call to asInstanceOf), but this is where I’m expecting the developer using my class to be right. And where they’re right, what I don’t expect is a ClassCastException.
import scala.collection.mutable.Map
object Main {
def printIt(optString: Option[String])
{
optString match
{
case x: Some[_] => println("Got: " + x.get.substring(0,2))
case None => println("Got nothing")
}
}
def withInferredType(token: Token)
{
println("\n--- With Inferred Type ---")
val fish = token.get("fish")
printIt(fish)
}
def withoutExplicitType(token: Token)
{
println("\n--- Without Explicit Type ---")
val fish = token.get("fish")
fish match
{
case x: Some[_] => println("Got: " + x.get.substring(0,2))
case None => println("Got nothing")
}
}
def withExplicitType(token: Token)
{
println("\n--- With Explicit Type ---")
val fish = token.get[String]("fish")
fish match
{
case x: Some[_] => println("Got: " + x.get.substring(0,2))
case None => println("Got nothing")
}
}
def main(args: Array[String])
{
val token = new Token
token.put("fish", "cat")
withExplicitType(token)
withInferredType(token)
withoutExplicitType(token)
}
}
class Token()
{
private var elements = Map[String,Object]()
def get[A <: Object](key: String): Option[A] =
{
for (val value <- elements.get(key))
return Some(value.asInstanceOf[A])
return None
}
def put[A <: Object](key: String, value: A)
{
elements.put(key, value)
}
}
resulting in:
scalac Token.scala && scala Main
--- With Explicit Type ---
Got: ca
--- With Inferred Type ---
Got: ca
--- Without Explicit Type ---
java.lang.ClassCastException: java.lang.String cannot be cast to scala.runtime.RichString
at Main$.withoutExplicitType(Token.scala:29)
at Main$.main(Token.scala:52)
at Main.main(Token.scala)
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.ObjectRunner$$anonfun$run$1.apply(ObjectRunner.scala:75)
at scala.tools.nsc.ObjectRunner$.withContextClassLoader(ObjectRunner.scala:49)
at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:74)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:154)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
In all three cases I’m doing the same thing with the same data. In the ‘Inferred Type’ case, it’s actually doing the thing I want in production. But all three cases compile and one throws a ClassCastException – I had very much hoped that the type system would pick me up on the broken case.
What am I doing wrong, lazyweb? Any comments greatly appreciated.
Considered Harmful
Morning. I’ve had a think about it and as usual with me writing about Scala, it’s my mistake rather than the language’s. What I’d hoped to achieve was something like duck-typing where I could pull something out of the hash and use it as whatever made sense in the code. Static type inference doesn’t work this way though.
In the above code, in the two cases where I was constraining the type, it works fine. In the case where I don’t constrain the type, Scala can legitimately choose any type that implements substring; in this case it chose RichString, which happens not to be the type I put in to the hash. In general, this is a pretty dangerous approach – what I want is for the type system to force me to nominate a type for the result of the get before I use it.
Turns out the way to do this is to have get return Object and use asInstanceOf at the call site. C’est la vie.