I see no corresponding reason to delay longs.  Instead, I see a pressing
need to figure out the correct relation between switch (x) { case (byte)1; } where x might be a long or Long.  I don't see a way to delay that decision.

As outlined in Gavin's note about many things (patterns, generics, null, and primitives), I think there is a pretty good answer in there, which is to (at least initially) forbid non-type-restating numeric constant and primitive type test patterns.  This gives us the freedom to stop there, or expand in several directions regarding primitives (box-centric vs value-set) without committing to either right now.

The fundamental problem with numeric literals is that while there's only one way to convert 0.0d to Object (box to Double and widen), there are many ways to convert 0 to Object (box to any of { Byte, Short, Integer, Long, Character } and then widen).  The language helpfully provides automatic conversions so we are not irritated by being constantly asked "which zero did you intend to assign to this int?"  But this makes for some real trouble when we try to define the semantics of things like "x matches 0", when the static type of x is Object.  Should we match all of: (Byte) 0, (Short) 0, (Character) 0, (Integer) 0, (Long) 0, and maybe even float and double?  This adds a lot of complexity for very little gain.  Instead, we tell the user: "that's an imprecise question, please ask it more precisely."

We already have defined semantics of what switch does when the switch argument is a primitive or box, and the case label is a numeric constant.  So that question is answered.  The new question is, what if the switch argument is something broader, like Number or Object?  And the simple answer is: disallow numeric constant patterns here (with the possible exception of manifestly typed constants, like 0.0f, which only matches Float zero), because they're too imprecise.

Two observations:
 - This won't happen very often; its rare that you know so little about the type of a target, but are particularly concerned that it might be the number seven.  - When it does happen, there are easy ways to delegate to existing equality comparisons that are more explicit (such as type test patterns with guards.)

For example:

    Object o = ...
    switch (o) {
        case Integer n
            where n == 7: ...
        case Number n
            where n.intValue() == 7: ...
    }

So, we choose to take no heroic measures to try to decipher the relationship between numeric constants and broadly-typed targets like Number or Object.  Give me more type information, and you get more flexibility.

We do the same with primitive type test patterns; you don't get to say "case int" in the above switch, lest you think that it might be testing to see if the target is a boxed number that falls into the int value-set range.  Instead, you say "case Integer".  If the target type is narrower (say, Integer), then you can say "case int" because that's effectively type-restating.

Returning to floating point, this means that we need only define semantics of matching a floating-point context to something already known to be a float.








On 12/13/2017 7:51 PM, John Rose wrote:
Joe's points make perfect sense to me.

Because of distinct problems with float, double, and reference operand
types, the "==" operator of Java is a poor equivalence relation, so just
referring the semantics of switch to op== is IMO a false start for defining
switch.  Switch-on-string has already broken with that false start, in the
case of references, using Object.equals.  A coherent way to break from
op== on floats is to, also, refer to the closest possible Object.equals
method, that on Float (and Double).  Joe's proposal in fact appeals to
the same standards, that of floatToIntBits.

https://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#equals(java.lang.Object) <https://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#equals%28java.lang.Object%29>

The most fine-grained equality relation that can be defined across all
types does not have an API point, but it can be called "substitutability".
For references substitutability is simply acmp, or op==(Object,Object).
For floats, substitutability is approximated by equality on floatToIntBits,
but defined rigorously by equality on floatToRawIntBits, which preserves
distinctions among NaNs.  Since those distinctions can be observed by
code, two distinct NaNs cannot be said to be substitutable for each
other.

Joe's comparison, and that of Float.equals, is slightly more coarse-grained
of an equivalence relation, because all the NaNs are grouped into a single
cluster.  I wish the designer of Float.equals had not stopped arbitrarily at
NaN folding, and used floatToRawIntBits.  But, given that history, I think
when switch supports floats and doubles, it will use Joe's comparison.

As Remi points out, suitable third-party extractors (or value type wrappers)
can provide other relations besides Joe's default, either distinguishing
NaNs or lumping zeroes.  Perhaps even rejecting NaNs, since they aren't
equal to themselves, supposedly.

But we only get to set the default once.  So perhaps we should delay
supporting floats directly, until we can put all three or four float
matching predicates in front of us and decide which is the default.

I see no corresponding reason to delay longs.  Instead, I see a pressing
need to figure out the correct relation between switch (x) { case (byte)1; } where x might be a long or Long.  I don't see a way to delay that decision.

Backing up a bit, I prefer to evaluate match semantics in terms of assignment detection, rather than ad hoc equality predicates.  If the story is only ad
hoc, "if this type then this predicate" I am sure it will have more nasty
corners than it needs.  If it has an overarching principle, then I am sure
it will have nasty corners (as with +0 and NaNs), but only a minimum
of them.  And the overarching principle I prefer for match is to ask the
following polymorphic question:  "Could a value just like this case
expression have been assigned to that switch variable?"  This, IMO,
unwinds a lot of otherwise ad hoc special pleading.  It does require
some ad hoc definition of what "just like this" means, but the rest falls
out of prior JLS semantics.  Including the vexed questions which will
be occurring to you, above, about Long vs. long vs. byte.

— John

On Dec 12, 2017, at 1:52 PM, Remi Forax <[email protected] <mailto:[email protected]>> wrote:

While we could do that, use bits representation for float and double, this is typically the kind of things that a user can also do with a record (a value type record ?) and a deconstructor, so in my opinion, we should not rush to implement this kind of switch given that we will soon provide a general mechanism to implement them outside of the JDK.

Rémi

------------------------------------------------------------------------

    *De:*"Brian Goetz" <[email protected]
    <mailto:[email protected]>>
    *À:*"amber-spec-experts" <[email protected]
    <mailto:[email protected]>>
    *Envoyé:*Lundi 11 Décembre 2017 22:25:34
    *Objet:*Switching on float/double/long

    A target of opportunity for the new switch JEP is to fill out the
    set of types that traditional switches can operate on --
    specifically float, double, and long.  The reason that we don't
    support these now is mostly an accident of history; the
    `tableswitch` and `lookupswitch` opcodes are int-based, so the
    compiler doesn't have a convenient target for translating these. 
    As you've seen from the recent notes on switch translation, we're
    working towards using indy more broadly as a translation target
    for most switch constructs.  This makes it far easier to bust the
    limitations on switch argument types, and so this has been listed
    as a target of opportunity in the JEP (for both statement and
    expression switches.)

    Our resident floating-point expert, Joe Darcy, offers the
    following additional thoughts on the subject:

    -- Begin forwarded message

    Per a recent request from Brian, I've written a few thoughts
    about switching on floating-point values.

    To address some common misunderstandings of floating-point, while
    it is often recommended to*not*compare floating-point values for
    equality, it is perfectly well-defined to do such comparisons, it
    just might not do what you want

    For example, instead of

        // Infinite loop since sum stored in d never exactly equals
    1.0, doh!
        while(d != 1.0)\u000B
            d += 0.1;

    use either

        // Counted loop
        for(int i = 0; i < 10; i++)\u000B
            d += 0.1;

    or

        // Stop when numerical threshold is met
        while(d <= 1.0)\u000B
            d += 0.1;

    depending on the semantics the loop is trying to capture.

    I've attached a slide from my JVMLS talk this year to help
    illustrate the semantic modeling going in in IEEE 754
    floating-point. Each of the 232possible bit patterns of a float
    is some floating-point value, likewise for the 264possible bit
    patterns of a double. However, from a Java language or JVM
    perspective, there are not 232or 264distinct values we need or
    want to distinguish in most cases. In particular, we almost
    always want to treat all bit patterns which encode a NaN as a
    single conceptual NaN. Another wrinkle concerns zero: IEEE 754
    has both a positive zero and a negative zero. Why are
    there*two*zeros? Because there are two infinities.  The signed
    infinities and distinguished by divide (1.0/0.0 => +infinity,
    1.0/-0.0 => -infinity) and by various library functions.

    So we want to:

        * Allow every distinct finite nonzero floating-point value to
    be  the case of a switch.
        * Allow -0.0 and +0.0 to be treated separately.
        * Allow -infinity and +infinity to be treated separately.
        * Collapse all NaN representation as a single value.

    For the "Rounding" mapping in the diagram which goes from the
    extended real numbers to floating-point data, there is a nonempty
    segment of the real number line which maps to a given
    representable floating-point number. For example, besides the
    string "1.0" mapping exactly to the reprentable floating-point
    value 1.0, there is a region slightly small than 1
    (0.99999999999999999999...) which will round up to 1.0 and a
    region slightly larger than 1 (1.000000000000000001...) which
    will round down to 1 from decimal -> binary conversion. This
    would need to be factored into any distinctiveness requirements
    for the different arms of the switch. In other words

        case 1.000000000000000001:
        ....
        case 0.99999999999999999999
        ...

    would need to be rejected just as

        case 0:
        ....
        case 00:

    is rejected.

    In terms of JDK 9 structures and operations, the following
    transformation of a float switch has what I think are reasonable
    semantics:

        Replace each float case label y in the source with an int
    label resulting from floatToIntBits(y). Note that floatToIntBits
    is used for the mapping rather than floatToRawIntBits since we
    want NaNs to be grouped together.

        Instead of switching on float value x, switch on
    floatToIntBits(x).

    HTH,

    -Joe



Reply via email to