Proposal to drop Weak Conformance from the language

Well, even more reason to get rigth now :slight_smile:
But thanks, TIL:

scala> List('x', 1) ++ List(1.0)
res1: List[AnyVal] = List(120, 1, 1.0)

scala> List('x', 1, 1.0)
res2: List[Double] = List(120.0, 1.0, 1.0)
  1. Yes. If inference to AnyVal were to be treated as an error, it should apply to all expressions consistently.
  2. The special-casing of literal expression is what’s proposed in this SIP proposal, not me. Under this proposal, some code that has been inferred as List[Double] etc for ten years would now infer as List[AnyVal] by switching to Scala 3. By removing weak conformance, inference would fall back to LUBing. If we are removing weak conformance (which I am for), neither List(1 + 1, 5.0) nor List(2, 5.0) should compile without explicit type annotations.
1 Like

Regarding the inference of AnyVal, TBH I’m surprised there are so strong feedback in the line of "if we drop weak conformance, we absolutely must error on inferring AnyVal". Dropping weak conformance does not qualitatively create more opportunities to infer “stupid” stuff instead of erroring. Already today, if you write List('a', "b") you get a List[Any], which is not any better than List[AnyVal]. And List(true, 5) actually infers List[AnyVal] already as well. The proposal will not fundamentally change the situation regarding inferences of top types.

And frankly, if I do write List(5.5f, 5L, '5'), I would much rather get a List[AnyVal] with those values rather than List[Float](5.5f, 5.0f, 53.0f), which is what we get currently.

4 Likes

I find the coercion of char literals to numbers particularly jarring. I know that for legacy computer science reasons that chars are a numeric type, but semantically they are not. They are atoms of text.

I think I’m on record as saying that I don’t like implicit type coercions, whether through implicit magicary or compiler-magic conformance. I would rather it was either explicitly annotated by users, or hidden behind an import. Any transitory benefit to somebody writing their very first script is rapidly outweighed by the bugs and head-scratchers that you end up with later on in real code IMHO. I am far more comfortable with polymorphic literals than I am with the compiler stepping in to decide that I actually meant to write something different from what I wrote.

1 Like

Actually I would like to have the warning for inference to Any* and use all opportynity to make that happens :slight_smile:
(and if it was only for me, as I said previously, I’m all in favor of no weak conformance at all and compiler error everywhere)

Oh, you get List[Float]? That’s bad, it should be List[Double]. :slight_smile:

Seriously, since today most hardware platforms are 64 bit, there is little to no advantage of using any floating point type other than Double. :slight_smile:

Characters being numbers can come in handy sometimes:

**Welcome to Scala 2.12.4 (OpenJDK 64-Bit Server VM, Java 1.8.0_191).
Type in expressions for evaluation. Or try :help.

(‘a’ to ‘z’).mkString(", ")
res2: String = a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z**

But I’m pretty sure no one who writes ‘a’ wants it to be converted to a Float or Double.

Similar here: I always wanted a supertype of all numeric types (e.g. Number), so this is just one more opportunity to bring it up. :slight_smile:

In that case, you would have List[Number].

I think what you actually want is [N] given Number[N] => List[N]

I don’t understand what you mean, could you give an example?

I think what @drdozer is trying to say is that the least upper bound for Int and Double is Number. This doesn’t happen because Scala lets us pretend to use primitive types in generics. If you explicitly state that you want the boxed types, then you do get Number.

scala> List(1: java.lang.Integer, 3.0: java.lang.Double)
res12: List[Number with Comparable[_ >: Double with Integer <: Number with Comparable[_ >: Double with Integer <: Number]]] = List(1, 3.0)

since today most hardware platforms are 64 bit, there is little to no advantage of using any floating point type other than Double

This is an aside from the main conversation, but there are definitely reasons to use 32-bit floats:

  1. Half the storage size, which also gives runtime speedups due to using less CPU cache and less RAM, disk and network bandwidth. If calculation precision is an issue (a good default assumption), you can still calculate in double mode and then store results as floats.
  2. If calculation precision is not an issue, then floats use cache space and RAM bandwidth twice as efficiently. And if the jitter can vectorize your code to sse/avx/etc., you can do twice as much work per clock using floats. (On that note, the LMS Intrinsics project sounds very cool; I haven’t read beyond the first page though.)
1 Like

With regards to boxing, for a hypothetical Number type, defined as LUB of all primitive number types, List[Number] is no different than List[AnyVal].

@sjrd Allowing literals to harmonize seems to cater for the special cases where writing lots of numerics (floats, doubles, longs…) and the “overhead” of F/D/L on every value is annoying. To cater to that need has the SIP committee considered introducing a language flag?

Language flags in general we basically want to banish. Every language flag introduces twice as many untested configurations for the interaction of language features.

2 Likes

For me original proposal is fine. Only thing that makes me uncomfortable is that literal int, inline field int, and normal val/var/def of type Int are treated differently, but I understand that this is safer way.

Other thing is that Array[AnyVal] and Array[Any] is almost always not what you want and there should be at least warning when it is inferred.

Oh… and proposition with using Union types is neat but to risky. Unions are new stuff and we don’t know how it’ll be used in ecosystem, where it fits and where are its limitations. Original proposal is just simple fix that’ll bring more predictable behavior. Proposition with Union types does revolution that could go wrong in so many ways (even if i think it is nice and cleaver).

For the record, there is already a compiler option (-Xlint:infer-any) which warns when Any and friends are inferred:

scala> List(1, "s")
<console>:12: warning: a type was inferred to be `Any`; this may indicate a programming error.
       List(1, "s")
       ^
res3: List[Any] = List(1, s)

scala> List(1, true)
<console>:12: warning: a type was inferred to be `AnyVal`; this may indicate a programming error.
       List(1, true)
       ^
res4: List[AnyVal] = List(1, true)

It doesn’t work yet for Serializable and the other usual suspects, but that’s not really relevant to this discussion…

I understand that there are difficulties, but since conceptually this is the most straightforward model of how we think about numbers, I hope you guys will reconsider type intersections for literals.

If you’re going to special-case something, I’d much rather it be special-casing of rapid type intersection of literals, with heuristics for widening the type to a single type early enough to avoid the more problematic search issues, than to say that Int alone has weird magic properties to not stay an Int.

Yes, it’s more work, but it’s also more clean.

2 Likes

Good to hear. Are there plans to remove the existing ones?

Yes: