I like @propensive’s proposal, but if that’s impractical, here’s another view:
Like @soronpo mentioned, there’s a view in which implicit widening and implicit conversion (which are currently separate) could be unified, by replacing the former with the latter.
Provide implicit conversions (maybe behind an import, or maybe in predef) where there’s no loss of precision – Byte => Int
, Int => Long
, Float => Double
, Int => Double
etc.
Then, look at it from the “principle of least surprise”. Let’s say the List
, Array
, etc. constructors were “builders” instead:
val foo = Array().add(1).add(1.0).add(1.0f).build()
// instead of
val foo = Array(1, 1.0, 1.0f)
Then, you’d expect the type of the result to be decided by the first thing you add. So I’d propose that the first argument to the constructor should decide the type, and further arguments would have to be either the same type, or be implicitly converted to the same type. If that can’t happen, you get a reasonable compile error that it’s the wrong type.
If the conversions were placed in predef, this would render most usages of literal-based collection construction source compatible (except in cases like Array(0, 1.0, 3.0)
, in which case they would have to change the first element of the constructor. Hashtag sorrynotsorry)
This would also probably simplify the typechecker considerably, especially if it were generally applied to varargs methods. I think it’s a reasonable constraint that I personally wouldn’t mind being applied to all of my code. In particular, users of -Xfatal-warnings -Ywarn-numeric-widen
, which is a thing that lots of people (including myself) frequently cargo-cult from Rob Norris, wouldn’t be affected at all. Others would get a reasonable, easily-corrected (possibly even automatically correctable?) compile-time error.