Hi Scala Community!
This thread is the SIP Committee’s request for comments on a proposal to remove Weak Conformance from the language. You can find all the details here.
In some situations, Scala uses a weak conformance relation when testing type compatibility or computing the least upper bound of a set of types. The principal motivation behind weak conformance was to make an expression like this have type
List(1.0, math.sqrt(3.0), 0, -3.3) // : List[Double]
It’s “obvious” that this should be a
List[Double]. However, without some special provision, the least upper bound of the lists’s element types
(Double, Double, Int, Double) would be
AnyVal, hence the list expression would be given type
A less obvious example is the following one, which was also typed as a
List[Double], using the weak conformance relation.
val n: Int = 3 val c: Char = 'X' val n: Double = math.sqrt(3.0) List(n, c, d) // used to be: List[Double], now: List[AnyVal]
Here, it is less clear why the type should be widened to
List[AnyVal] seems to be an equally valid – and more principled – choice.
Weak conformance applies to all “numeric” types (including
Char), and independently of whether the expressions are literals or not. However, in hindsight, the only intended use case is for integer literals to be adapted to the type of the other expressions. Other types of numerics have an explicit type annotation embedded in their syntax (
Chars) which ensures that their author really meant them to have that specific type).
Therefore, we propose to drop the general notion of weak conformance, and instead keep only one rule:
Int literals (only) are adapted to other numeric types if necessary. This rule yields the following results as examples:
inline val b = 33 def f(): Int = b + 1 Array(b, 33, 5.5) : Array[Double] // b is an inline val Array(f(), 33, 5.5) : Array[AnyVal] // f() is not a constant Array(5, 11L) : Array[Long] Array(5, 11L, 5.5) : Array[AnyVal] // Long and Double found Array(1.0f, 2) : Array[Float] Array(1.0f, 1234567890): Array[AnyVal] // loss of precision Array(b, 33, 'a') : Array[Char] Array(5.toByte, 11) : Array[Byte]
The changes in weak conformance mostly change the inferred type of some expressions, typically from a precise numeric type such as
AnyVal. In most cases, the new inferred type will result in a type error soon after the given expression, which can be fixed by using explicit calls to
.toDouble (or similar) on the subexpressions that the user wants to be converted.
In some cases, it is possible that the new code does not trigger a compile error, and might subtly change some behavior at run-time. For example, the following snippet:
def sameClass[T](xs: T*): Boolean = xs.tail.forall(_.getClass == xs.head.getClass) val x: Int = 5 sameClass(x, 5.5)
will compile before and after the change, but will display
true in Scala 2 and
false in Scala 3.
Opening this Proposal for discussion by the community to get a wider perspective and use cases.