<div dir="ltr">This makes a lot of sense. I'm wondering, though, how it works with float and double. I don't see the answer by looking at JLS Ch5. Are the following expressions legal, and if so which ones are true?<div><ol><li>2.0 instanceof int</li><li>2.5 instanceof int</li><li>Double.MAX_VALUE instanceof float</li><li>Math.PI instanceof float</li><li>Double.NaN instanceof float</li><li>Double.NaN instanceof double</li></ol><div>Intuitively, I think I would expect that if t is an expression of primitive type T, and U is also a primitive type, then <font face="monospace">t instanceof U</font><font face="arial, sans-serif"> is true iff</font> t == (T) (U) t. (Perhaps with a variant of == that is reflexive even for NaN, or perhaps not.) That would make (1) true, (2,3,4) false, and (5,6) who knows.</div></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, 8 Sept 2022 at 09:53, Brian Goetz <<a href="mailto:brian.goetz@oracle.com">brian.goetz@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

  
  <div>
    <font size="4"><font face="monospace">Earlier in the year we talked
        about primitive type patterns.  Let me summarize<br>
        the past discussion, what I think the right direction is, and
        why this is (yet<br>
        another) "finishing up the job" task for basic patterns that, if
        left undone,<br>
        will be a sharp edge.<br>
        <br>
        Prior to record patterns, we didn't support primitive type
        patterns at all. With<br>
        records, we now support primitive type patterns as nested
        patterns, but they are<br>
        very limited; they are only applicable to exactly their own
        type.  <br>
        <br>
        The motivation for "finishing" primitive type patterns is the
        same as discussed<br>
        earlier this week with array patterns -- if pattern matching is
        the dual of<br>
        aggregation, we want to avoid gratuitous asymmetries that let
        you put things<br>
        together but not take them apart.  <br>
        <br>
        Currently, we can assign a `String` to an `Object`, and recover
        the `String`<br>
        with a pattern match:<br>
        <br>
            Object o = "Bob";<br>
            if (o instanceof String s) { println("Hi Bob"); }<br>
        <br>
        Analogously, we can assign an `int` to a `long`:<br>
        <br>
            long n = 0;<br>
        <br>
        but we cannot yet recover the int with a pattern match:<br>
        <br>
            if (n instanceof int i) { ... } // error, pattern `int i`
        not applicable to `long`<br>
        <br>
        To fill out some more of the asymmetries around records if we
        don't finish the job: given <br>
        <br>
            record R(int i) { }<br>
        <br>
        we can construct it with<br>
        <br>
            new R(anInt)     // no adaptation<br>
            new R(aShort)    // widening<br>
            new R(anInteger) // unboxing<br>
        <br>
        but yet cannot deconstruct it the same way:<br>
        <br>
            case R(int i)     // OK<br>
            case R(short s)   // nope<br>
            case R(Integer i) // nope<br>
        <br>
        It would be a gratuitous asymmetry that we can use pattern
        matching to recover from<br>
        reference widening, but not from primitive widening.  While many
        of the<br>
        arguments against doing primitive type patterns now were of the
        form "let's keep<br>
        things simple", I believe that the simpler solution is actually
        to _finish the<br>
        job_, because this minimizes asymmetries and potholes that users
        would otherwise<br>
        have to maintain a mental catalog of.  <br>
        <br>
        Our earlier explorations started (incorrectly, as it turned
        out), with<br>
        assignment context.  This direction gave us a good push in the
        right direction,<br>
        but turned out to not be the right answer.  A more careful
        reading of JLS Ch5<br>
        convinced me that the answer lies not in assignment conversion,
        but _cast<br>
        conversion_.  <br>
        <br>
        #### Stepping back: instanceof<br>
        <br>
        The right place to start is actually not patterns, but
        `instanceof`.  If we<br>
        start here, and listen carefully to the specification, it leads
        us to the<br>
        correct answer.  <br>
        <br>
        Today, `instanceof` works only for reference types. 
        Accordingly, most people<br>
        view `instanceof` as "the subtyping operator" -- because that's
        the only<br>
        question we can currently ask it.  We almost never see
        `instanceof` on its own;<br>
        it is nearly always followed by a cast to the same type. 
        Similarly, we rarely<br>
        see a cast on its own; it is nearly always preceded by an
        `instanceof` for the<br>
        same type.  <br>
        <br>
        There's a reason these two operations travel together: casting
        is, in general,<br>
        unsafe; we can try to cast an `Object` reference to a `String`,
        but if the<br>
        reference refers to another type, the cast will fail.  So to
        make casting safe,<br>
        we precede it with an `instanceof` test.  The semantics of
        `instanceof` and<br>
        casting align such that `instanceof` is the precondition test
        for safe casting.<br>
        <br>
        > instanceof is the precondition for safe casting<br>
        <br>
        Asking `instanceof T` means "if I cast this to T, would I like
        the answer."<br>
        Obviously CCE is an unlikable answer; `instanceof` further
        adopts the opinion<br>
        that casting `null` would also be an unlikable answer, because
        while the cast<br>
        would succeed, you can't do anything useful with the result.<br>
        <br>
        Currently, `instanceof` is only defined on reference types, and
        on this domain<br>
        coincides with subtyping.  On the other hand, casting is defined
        between<br>
        primitive types (widening, narrowing), and between primitive and
        reference types<br>
        (boxing, unboxing).  Some casts involving primitives yield
        "better" results than<br>
        others; casting `0` to `byte` results in no loss of information,
        since `0` is<br>
        representable as a byte, but casting `500` to `byte` succeeds
        but loses<br>
        information because the higher order bits are discarded.  <br>
        <br>
        If we characterize some casts as "lossy" and others as "exact"
        -- where lossy<br>
        means discarding useful information -- we can extend the "safe
        casting<br>
        precondition" meaning of `instanceof` to primitive operands and
        types in the<br>
        obvious way -- "would casting this expression to this type
        succeed without error<br>
        and without information loss."  If the type of the expression is
        not castable to<br>
        the type we are asking about, we know the cast cannot succeed
        and reject the<br>
        `instanceof` test at compile time.<br>
        <br>
        Defining which casts are lossy and which are exact is fairly
        straightforward; we<br>
        can appeal to the concept already in the JLS of "representable
        in the range of a<br>
        type."  For some pairs of types, casting is always exact (e.g.,
        casting `int` to<br>
        `long` is always exact); we call these "unconditionally exact". 
        For other pairs<br>
        of types, some values can be cast exactly and others cannot.  <br>
        <br>
        Defining which casts are exact gives us a simple and precise
        semantics for `x<br>
        instanceof T`: whether `x` can be cast exactly to `T`. 
        Similarly, if the static<br>
        type of `x` is not castable to `T`, then the corresponding
        `instanceof` question<br>
        is rejected statically.  The answers are not suprising:<br>
        <br>
         - Boxing is always exact;<br>
         - Unboxing is exact for all non-null values;<br>
         - Reference widening is always exact;<br>
         - Reference narrowing is exact if the type of the target
        expression is a<br>
           subtype of the target type;<br>
         - Primitive widening and narrowing are exact if the target
        expression can be<br>
           represented in the range of the target type.<br>
        <br>
        #### Primitive type patterns<br>
        <br>
        It is a short hop from `instanceof` to patterns (including
        primitive type<br>
        patterns, and reference type patterns applied to primitive
        types), which can be<br>
        defined entirely in terms of cast conversion and exactness: <br>
        <br>
         - A type pattern `T t` is applicable to a target of type `S` if
        `S` is<br>
           cast-convertible to `T`;<br>
         - A type pattern `T t` matches a target `x` if `x` can be cast
        exactly to `T`;<br>
         - A type pattern `T t` is unconditional at type `S` if casting
        from `T` to `S`<br>
           is unconditionally exact;<br>
         - A type pattern `T t` dominates a type pattern `S s` (or a
        record pattern<br>
           `S(...)`) if `T t` would be unconditional on `S`.<br>
        <br>
        While the rules for casting are complex, primitive patterns add
        no new<br>
        complexity; there are no new conversions or conversion
        contexts.  If we see:<br>
        <br>
            switch (a) { <br>
                case T t: ...<br>
            }<br>
        <br>
        we know the case matches if `a` can be cast exactly to `T`, and
        the pattern is<br>
        unconditional if _all_ values of `a`'s type can be cast exactly
        to `T`.  Note<br>
        that none of this is specific to primitives; we derive the
        semantics of _all_<br>
        type patterns from the enhanced definition of casting.<br>
        <br>
        Now, our record deconstruction examples work symmetrically to
        construction: <br>
        <br>
            case R(int i)     // OK<br>
            case R(short s)   // test if `i` is in the range of `short`<br>
            case R(Integer i) // box `i` to `Integer`<br>
        <br>
        <br>
      </font></font>
  </div>

</blockquote></div>