Why is implicit transformation of numerical types inconsistent between “for/comprehension” expressions compared to(!) assignment operations?

大城市里の小女人 提交于 2019-12-11 04:35:59

问题


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:

  1. To get to RichLong's to method, the Int would have to be first converted into a Long, and then into a RichLong, and Scala does not apply two chained implicit conversions, ever. It can only convert Int to RichInt or to Long, not Int to Long to RichLong.
  2. To apply such conversion, there would have to be some indication a Long was required in first place, and there isn't. The i: Long does not refer to the type of 0 to 1000000000L, whereas l: Long does refer to the type of 0 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!