- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Adding a trait by casting?
Wed, 2011-06-15, 19:51
Hello! There was a question on Stackoverflow:
"I wonder whether it is possible to mark primitive types with phantom types without having them boxed."( http://stackoverflow.com/questions/6358651/marking-primitive-types-with-phantom-types-in-scala/6358773#6358773 ) I was pretty sure the answer was "no", but Ken Bloom suggested this: trait IsPrime val x = 5.asInstanceOf[Int with IsPrime]
val y:Int = x val z:Int with IsPrime = 6 /* this line causes a compile error which is what you want */ I was pretty surprised that the cast in the second line succeeds, especially as in this case there is no "normal" way to produce that type, as Int is final. What do you think, is this a bug or a feature? Cheers, Daniel
Thu, 2011-06-16, 07:37
#2
Re: Re: Adding a trait by casting?
> On Wednesday, June 15, 2011 2:51:45 PM UTC-4, Daniel Gronau wrote:
> >
> > I was pretty sure the answer was "no", but Ken Bloom suggested this:
> >
> > trait IsPrimeval x = 5.asInstanceOf[Int with IsPrime]val y:Int = xval
> z:Int with IsPrime = 6 /* this line causes a compile error which is what you
> want */
> >
> > I was pretty surprised that the cast in the second line succeeds,
> especially as in this case there is no "normal" way to produce that type, as Int
> is final.
> >
> >
> An explicit cast is just that - it isn't statically type-checked and
> assumes
> success at compile time. You'll get a ClassCastException trying to run
> this,
> for instance:
>
> trait IsPrime
> val x = 5.asInstanceOf[Int with IsPrime]
> val y: IsPrime = x
>
But why isn't it rejected at *runtime*? Consider:
trait IsPrime{ def test{ println("prime!") } }
val x = 5.asInstanceOf[Int with IsPrime]
def double(z: Int with IsPrime):Int = 2*z
print(double(x)) // 10
print(x.test) // ClassCastException
How can x possibly act as an "Int with IsPrime" in the first print without problems, when the second print blows up? Don't you think this behavior is dangerous and "buggy"? So shouldn't the ClassCastException happen in asInstanceOf?
Cheers,
Daniel
Thu, 2011-06-16, 07:57
#3
Re: Adding a trait by casting?
The way I see it:
x is an Int, because asInstanceOf is following the rules
of Scala type erasure so 'with IsPrime' is erased.
=========================================================
api doc class Any:
def asInstanceOf[T0]: T0
Cast the receiver object to be of type T0.
Note that the success of a cast at runtime is modulo Scala's erasure
semantics.
Therefore the expression 1.asInstanceOf[String] will throw a
ClassCastException at runtime,
while the expression List(1).asInstanceOf[List[String]] will not. In
the latter example,
because the type argument is erased as part of compilation it is not
possible to check
whether the contents of the list are of the requested type.
===========================================================
scala> val x = 5.asInstanceOf[Int with IsPrime]
x: Int with IsPrime = 5
scala> x.isInstanceOf[Int]
res5: Boolean = true
scala> x.isInstanceOf[Int with IsPrime]
res6: Boolean = false
Thu, 2011-06-16, 17:17
#4
Re: Re: Adding a trait by casting?
On Thu, Jun 16, 2011 at 03:30, Daniel Gronau wrote:
> How can x possibly act as an "Int with IsPrime" in the first print without problems, when the second print blows up? Don't you think this behavior is dangerous and "buggy"? So shouldn't the ClassCastException happen in asInstanceOf?
You seem to be laboring under a misconception here. It seems you think
asInstanceOf should protect you against trouble, when, in fact, it is
the very opposite of that: asInstanceOf means "let me shoot myself in
the foot, I know what I'm doing!".
Thu, 2011-06-16, 19:07
#5
Re: Re: Adding a trait by casting?
On Thu, Jun 16, 2011 at 11:07 AM, Daniel Sobral <dcsobral@gmail.com> wrote:
One could add, that if you want to be sure not to vomit at runtime, any asInstanceOf should be preceded by an affirmative isInstanceOf. If not, there are no guarantees.
You seem to be laboring under a misconception here. It seems you think
asInstanceOf should protect you against trouble, when, in fact, it is
the very opposite of that: asInstanceOf means "let me shoot myself in
the foot, I know what I'm doing!".
One could add, that if you want to be sure not to vomit at runtime, any asInstanceOf should be preceded by an affirmative isInstanceOf. If not, there are no guarantees.
Fri, 2011-06-17, 03:07
#6
Re: Re: Adding a trait by casting?
On 6/16/11 9:07 AM, Daniel Sobral wrote:
> You seem to be laboring under a misconception here. It seems you think
> asInstanceOf should protect you against trouble, when, in fact, it is
> the very opposite of that: asInstanceOf means "let me shoot myself in
> the foot, I know what I'm doing!".
That's a rather silly statement to make because he hasn't said anything
at all about asInstanceOf being supposed to protect him from trouble, he
is just confused because he thought that he understood the nature of the
trouble it causes and he turned out to be wrong. In particular, his
point is just (as I would paraphrase it) that it is incredibly strange
and unintuitive for
x.asInstanceOf[T].isInstanceOf[T] == false
for any x and T, especially given that this is not true for Java ---
that is in Java casting might fail at the point that you do it, but if
it succeeds you then henceforth you are guaranteed that you have a value
of type T.
Think about it this way: because of this behaviour, unlike the type
system of Java, the type system of Scala does *not* provide the
guarantee that when you are given a value x of type T that the value
*really is* of type T, so in that respect it is *weaker* than Java. "So
just don't use asInstanceOf!" you might say. Well, that's all well and
good for me in my own code, but what if someone else does it? Then I
will see a random "ClassCastException" at some point in my code with
absolutely no idea why its there since I haven't done any casting in my
code! This kind of lack of safety is the kind of thing I'd expect in a
language like C++, not in Scala. :-)
Now presumably it was decided that this lack of safety and the weakness
it introduces into the Scala type system was a fair trade-off for not
having to take the performance hit of having asInstanceOf do special
checks to make sure that the value it was given has the correct type ---
and being the pragmatic sort myself I am actually mostly okay with this
if that was indeed the thinking behind it. :-) However, the fact
remains that this behaviour *is* strange and unintuitive and it *does*
break the Scala type system in a way that in an important respect makes
the Scala type system *less* safe than the Java type system, and one can
bring this issue up, discuss it, and reflect on it without having this
imply that one is "laboring under a misconception" that "asInstanceOf
should protect you against trouble".
Cheers,
Greg
Fri, 2011-06-17, 03:17
#7
Re: Re: Adding a trait by casting?
On 17/06/11 02:07, Daniel Sobral wrote:
> asInstanceOf means "let me shoot myself in
> the foot, I know what I'm doing!".
More formally, acknowledging legitimacy of asInstanceOf is equivalent to
making the following logical claim:
forall P Q. P implies Q
...which is easy to refute by counter-example. There exists values for
P/Q such that P implies Q is false P=1 Q=0.
We *must* use these "lies" in our programs (halting problem)[1] and so
we must rephrase the question, "under what circumstances must we concede
and incur the penalties imposed by such a lie as asInstanceOf?" It turns
out that the answer is "Never, except under such circumstances as we
have already made a grave error." However, to be clear, this is not to
say that we must avoid lies, just that we can minimise their undesired
consequence (unsafe programs) and maximise their utility (which varies a
lot). Within some reasonable constraints, the asInstanceOf lie has no
use-case that pays off.
[1] Although, in my experience, there are a growing number of people who
are of the opinion that turing-completeness is over-rated.
Fri, 2011-06-17, 05:57
#8
Re: Re: Adding a trait by casting?
On Thu, Jun 16, 2011 at 7:00 PM, Gregory Crosswhite <gcrosswhite@gmail.com> wrote:
for any x and T, especially given that this is not true for Java --- that is in Java casting might fail at the point that you do it, but if it succeeds you then henceforth you are guaranteed that you have a value of type T.
Think about it this way: because of this behaviour, unlike the type system of Java, the type system of Scala does *not* provide the guarantee that when you are given a value x of type T that the value *really is* of type T, so in that respect it is *weaker* than Java.
In Java that's not true. Casting and type erasure interact in a way that says an expression of type T may successfully compute a value that isn't a T. Java's covariant arrays are also exceptions that rule and you don't even need casting operations to see that misbehavior.
Fri, 2011-06-17, 15:47
#9
Re: Re: Adding a trait by casting?
On 6/16/11 9:47 PM, James Iry wrote:
> In Java that's not true. Casting and type erasure interact in a way
> that says an expression of type T may successfully compute a value
> that isn't a T. Java's covariant arrays are also exceptions that rule
> and you don't even need casting operations to see that misbehavior.
That's a fair point. While it is true in Java that
x.asInstanceOf[T].isInstanceOf[T] == true
for any x and T in the Java type system, you can still get
ClassCastExceptions when working with data structures that you casted to
the wrong erased type parameter. On the other hand, in Java you only
have this problem for generic types, whereas in Scala you also have this
problem for non-generic types if they have mixins.
All in all this really makes me frustrated with the limitations of the
JVM more than anything...
Cheers,
Greg
Fri, 2011-06-17, 16:37
#10
Re: Re: Adding a trait by casting?
Wrt. “weakness” of the type system (which is strange to say while you are actively switching if off using .asInstanceOf[]) compared to Java: the mechanisms for type safety after erasure are dictated by the JVM and are completely identical in Java and Scala. The OP’s problem was just that he invented a type which simply cannot be represented during runtime, where Scala does the—arguably wrong—thing of using just the first of the types for the JVM representation.
scala> object A { val x = 1.asInstanceOf[Int with String] }
defined module A
scala> :javap -c -p A
Compiled from ""
public final class A$ extends java.lang.Object implements scala.ScalaObject{
public static final A$ MODULE$;
private final int x;
[...]
public A$();
Code:
0: aload_0
1: invokespecial #21; //Method java/lang/Object."":()V
4: aload_0
5: putstatic #23; //Field MODULE$:LA$;
8: aload_0
9: iconst_1
10: invokestatic #29; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
13: invokestatic #33; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
16: putfield #18; //Field x:I
19: return
}
Notice that no runtime type checking is necessary as all is guaranteed by the byte code verifier anyway. Now observe:
scala> object A { val x = 1.asInstanceOf[String with Int] }
defined module A
scala> A.x
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at A$.(:21)
at A$.()
at .(:23)
at .()
at .(:11)
at .()
at $export()
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.IMain$ReadEvalPrint.call(IMain.scala:592)
at scala.tools.nsc.interpreter.IMain$Request$$anonfun$10.apply(IMain.scala:828)
at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
at scala.tools.nsc.io.package$$anon$2.run(package.scala:31)
at java.lang.Thread.run(Thread.java:680)
Why is that? Because the private field is actually of type String, which then blows up during initialization:
scala> :javap -c -p A
Compiled from ""
public final class A$ extends java.lang.Object implements scala.ScalaObject{
public static final A$ MODULE$;
private final java.lang.String x;
[...]
public A$();
Code:
0: aload_0
1: invokespecial #21; //Method java/lang/Object."":()V
4: aload_0
5: putstatic #23; //Field MODULE$:LA$;
8: aload_0
9: iconst_1
10: invokestatic #29; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
13: checkcast #31; //class java/lang/String
16: putfield #18; //Field x:Ljava/lang/String;
19: return
}
Notice the “checkcast” operations inserted where the .asInstanceOf[] was in the source code.
To sum it up: if you restrict yourself to using only types which could actually exist in Java, runtime type checking is identical to that other language.
Regards,
Roland
On Jun 17, 2011, at 04:00 , Gregory Crosswhite wrote:
> On 6/16/11 9:07 AM, Daniel Sobral wrote:
>> You seem to be laboring under a misconception here. It seems you think
>> asInstanceOf should protect you against trouble, when, in fact, it is
>> the very opposite of that: asInstanceOf means "let me shoot myself in
>> the foot, I know what I'm doing!".
>
> That's a rather silly statement to make because he hasn't said anything at all about asInstanceOf being supposed to protect him from trouble, he is just confused because he thought that he understood the nature of the trouble it causes and he turned out to be wrong. In particular, his point is just (as I would paraphrase it) that it is incredibly strange and unintuitive for
>
> x.asInstanceOf[T].isInstanceOf[T] == false
>
> for any x and T, especially given that this is not true for Java --- that is in Java casting might fail at the point that you do it, but if it succeeds you then henceforth you are guaranteed that you have a value of type T.
>
> Think about it this way: because of this behaviour, unlike the type system of Java, the type system of Scala does *not* provide the guarantee that when you are given a value x of type T that the value *really is* of type T, so in that respect it is *weaker* than Java. "So just don't use asInstanceOf!" you might say. Well, that's all well and good for me in my own code, but what if someone else does it? Then I will see a random "ClassCastException" at some point in my code with absolutely no idea why its there since I haven't done any casting in my code! This kind of lack of safety is the kind of thing I'd expect in a language like C++, not in Scala. :-)
>
> Now presumably it was decided that this lack of safety and the weakness it introduces into the Scala type system was a fair trade-off for not having to take the performance hit of having asInstanceOf do special checks to make sure that the value it was given has the correct type --- and being the pragmatic sort myself I am actually mostly okay with this if that was indeed the thinking behind it. :-) However, the fact remains that this behaviour *is* strange and unintuitive and it *does* break the Scala type system in a way that in an important respect makes the Scala type system *less* safe than the Java type system, and one can bring this issue up, discuss it, and reflect on it without having this imply that one is "laboring under a misconception" that "asInstanceOf should protect you against trouble".
>
> Cheers,
> Greg
Fri, 2011-06-17, 16:57
#11
Re: Re: Adding a trait by casting?
A few points in response.
First and most foremost, thank you for the detailed and insightful
technical response which explains exactly what is going on! :-)
Second, I suppose my point is that I had never (until now) really
thought of asInstanceOf as actively switching off the type system so
much as adding an oracle to the process --- that is, as part of our
proof we assert at runtime that the VM will say that the cast is
actually okay and if this turns out to be wrong then we throw an
exception. But what I have learned is that it turns out that when you
add type erasure our oracle no longer has the full set of information at
its disposal to verify the cast after all and so it no longer provides a
full proof that our cast is okay.
Third, Scala *could* actually do something in general that would fix the
problem, which is whenever it sees a cast to a type of form "A with B
with C with ..." it could perform isInstanceOf checks for all of the
types that are erased. This would restore some safety to asInstanceOf.
Presumably they do not do this because they believe that it is better to
have random errors pop up in other places of the program if a programmer
actually got this cast wrong than to incur an additional runtime cost
Fri, 2011-06-17, 17:07
#12
Re: Re: Adding a trait by casting?
On 6/17/11 8:47 AM, Gregory Crosswhite wrote:
Oops, by "in general" I obviously really meant "in general *for types of the form "A with B with C with ...", since casts to types of the form List[_] can never be made safe in general on the JVM, unfortunately.
Also, this presumes that I have an intuition for how mixins of the form "A with B with C with ..." work, which obviously could be entirely wrong. :-)
Cheers,
Greg
4DFB7703 [dot] 9000007 [at] gmail [dot] com" type="cite">Third, Scala *could* actually do something in general that would fix the problem, which is whenever it sees a cast to a type of form "A with B with C with ..."
Oops, by "in general" I obviously really meant "in general *for types of the form "A with B with C with ...", since casts to types of the form List[_] can never be made safe in general on the JVM, unfortunately.
Also, this presumes that I have an intuition for how mixins of the form "A with B with C with ..." work, which obviously could be entirely wrong. :-)
Cheers,
Greg
An explicit cast is just that - it isn't statically type-checked and assumes success at compile time. You'll get a ClassCastException trying to run this, for instance:
trait IsPrimeval x = 5.asInstanceOf[Int with IsPrime]val y: IsPrime = x