问题
Why is desugaring and implicit transformation of numerical types inconsistent between "for/comprehension" expressions compared to(!) assignment operations?
I'm sure there are many general perspectives on this but I couldn't figure out a concise and logical explanation for the current behavior. [Ref:"Behavior of Scala for/comprehension..." ] For the sake of correctness all translations below was generated with the scala compiler ("scalac -Xprint:typer -e")
For example, during implicit numeric assignment transformation the Destination type is dominant:
Source: var l:Long = 0
Result : val l: Long = 0L
Source: var l:Long = 0.toInt
Result : var l: Long = 0.toInt.toLong
During implicit transformation of "for/comprehension" expressions the Source type is dominant:
Source: for (i:Long <- 0 to 1000000000L) { }
Result : 0.to(1000000000L).foreach(((i: Long) => ()))
Source: for (i <- 0L to 1000000000L) { }
Result : scala.this.Predef.longWrapper(0L).to(1000000000L).foreach[Unit](((i: Long) => ()))
回答1:
There are two completely different things going on. First, assignment:
val l: Long = 0
We have an Int
that is being assigned to a Long
. That shouldn't be possible, unless there is an implicit conversion from Int
to Long
, which we can verify like this:
scala> implicitly[Int => Long]
res1: Int => Long = <function1>
Since there is such a conversion, that conversion is applied.
Next, the for-comprehension:
for (i:Long <- 0 to 1000000000L) { }
This doesn't work because the to
method called on Int
(actually called on scala.runtime.RichInt
, through an implicit conversion) only admits an Int
argument, not a Long
argument.
The to
method called on a Long
(RichLong
) does admit a Long
argument, but there are two reasons why that doesn't apply on the expression above:
- To get to
RichLong
'sto
method, theInt
would have to be first converted into aLong
, and then into aRichLong
, and Scala does not apply two chained implicit conversions, ever. It can only convertInt
toRichInt
or toLong
, notInt
toLong
toRichLong
. - To apply such conversion, there would have to be some indication a
Long
was required in first place, and there isn't. Thei: Long
does not refer to the type of0 to 1000000000L
, whereasl: Long
does refer to the type of0
in the first example.
回答2:
I'm not sure what you mean by "destination type" and "source type", but I don't see any problem.
If you have an Int
, you can assign it to be a Long
. That's fine because the range of Int
is subset of the range of Long
. You can't pass a Long
when you expect an Int
because Long
has a larger range and, thus, might generate invalid Int
values. And the bit about to
methods is answered in your other question.
Going through your cases:
var l:Long = 0 // fine because Int up-casts to Long
var l:Long = 0.toInt // fine because Int up-casts to Long
for (i:Long <- 0 to 1000000000L) { } // bad because...
0 to 1000000000L // bad because RichInt.to doesn't accept a Long argument
for (i <- 0L to 1000000000L) { } // fine because...
0L to 1000000000L // fine because RichLong.to accepts a Long argument, and produces a Range of Longs
The cases in your for
examples have nothing to do with the for
. It only has to do with the fact that you are calling the to
method. An explanation:
0 to 1000000000L
is syntactic sugar for 0.to(1000000000L)
since to
is just a method. When you call to
on an Int
, the implicit conversion to RichInt
happens, so you're really calling (new scala.runtime.RichInt(0)).to(1000000000L)
. But, since RichInt
's to
method only accepts an Int
argument, passing in a Long
(ie, 1000000000L
) is illegal since Long
cannot be cast to Int
(since it might contain values that are out of Int
's range.
0L to 1000000000L
is syntactic sugar, again, for 0L.to(1000000000L)
. But now, since the to
method is being called on a Long
, the implicit conversion is to RichLong
: (new scala.runtime.RichLong(0L)).to(1000000L)
. And, since RichLong
's to
method accepts a Long
as a parameter, then everything is fine, because that's what you're giving it.
EDIT based on your comments:
It seems like your confusion here is derived from your belief that =
and to
should work the same way. They do not, and should not. The assignment operator, =
, is a very special keyword in Scala (and any language) whereas to
is not a keyword at all -- it's just a method that happens to be found on both RichInt
and RichLong
.
That said, there is still no inconsistency. Here's why:
Scala allows you to automatically cast up, but not down. The reason for this is very simple: if an B is is a kind of A, then an B can be substituted for a A without fear. The opposite is not true.
Lets assume you have two classes:
class A
class B extends A
val a: A = null
val b: B = null
So think about assignments:
val x: A = b // fine, since B is a subtype of A, so b *is* an A
val x: B = a // error, since As aren't necessarily Bs
Not lets look at function calls:
def f(x: A) {} // only accept As
f(b) // fine, because a B *is* an A
def g(x: B) {} // only accept Bs
g(a) // error, because As aren't necessarily Bs
So you can see, for both assignment operators and functions, you can substitute the a subtype for its supertype. If you think of Int
as a kind of Long
(with a more limited range), then it is perfectly analogous. Now, since methods are pretty much just functions sitting on a class, we would expect the behavior to be the same regarding arguments.
So think about what you are asking for when you say that RichInt.to(Int)
should be able to accept a Long
. This would mean that Scala would perform some automatic and safe conversion from Long
to Int
, which doesn't make sense.
FINALLY: if your real issue is that you just think that RichInt
should have a method to(Long)
, then, well, I guess that's something to complain to the language designers about. But they'd probably just tell you to use .toLong
and get on with your life.
来源:https://stackoverflow.com/questions/9905659/why-is-implicit-transformation-of-numerical-types-inconsistent-between-for-comp