Obvious correction: the `new` in the pattern examples was a cut and paste error, patterns don't say `new`.

On 9/10/2022 5:48 AM, Remi Forax wrote:


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

    *From: *"Brian Goetz" <[email protected]>
    *To: *"amber-spec-experts" <[email protected]>
    *Sent: *Saturday, September 10, 2022 2:16:15 AM
    *Subject: *Re: Array patterns (and varargs patterns)

    John pulled a nice Jedi-mind-trick on me, and pointed out that we
    actually have two creation expressions for arrays:

        new Foo[n]
        new Foo[] { a0, .., an }

    and that if we are dualizing, then we should have these two patterns:

        new Foo[] { P0, ..., Pn }  // matches arrays of exactly length N
        new Foo[P]                 // matches arrays whose length match P

    but that neither

        new Foo[] { P, Q, ... }   // previous suggestion
    nor
        new Foo[L] { P, Q }       // current suggestion

    correspond to either of those, which suggests that we may have
    prematurely optimized the pattern form.  The rational consequence
    of this observation is to do

        new Foo[] { P0, ..., Pn }  // matches arrays of exactly length N

    now (which is also the basis of varargs patterns), and once we
    have constant patterns (which are kind of required for the second
form to be all that useful), come back for `Foo[P]`.

I like this proposal, it offers a clean separation between the array pattern and a future spread pattern (or whatever when end up calling it).

Rémi



    On 9/6/2022 5:11 PM, Brian Goetz wrote:

        We dropped this out of the record patterns JEP, but I think it
        is time to revisit this.

        The concept of array patterns was pretty straightforward; they
        mimic the nesting and exhaustiveness rules of record patterns,
        they are just a different sort of container for nested
        patterns. And they have an obvious duality with array creation
        expressions.

        The main open question here was how we distinguish between
        "match an array of length exactly N" (where there are N nested
        patterns) and "match an array of length at least N".  We toyed
        with the idea of a "..." indicator to mean "more elements",
        but this felt a little forced and opened new questions.

        It later occurred to me that there is another place to nest a
        pattern in an array pattern -- to match (and bind) the
        length.  In the following, assume for sake of exposition that
        "_" is the "any" pattern (matches everything, binds nothing)
        and that we have some way to denote a constant pattern, which
        I'll denote here with a constant literal.

        There is an obvious place to put this (optional) pattern: in
        between the brackets.  So:

            case String[1] { P }:
                        ^ a constant pattern

        would match string arrays of length 1 whose sole element
        matches P.  And

            case String[] { P, Q }

        would match string arrays of length exactly 2, whose first two
        elements match P and Q respectively.  (If the length pattern
        is not specified, we infer a constant pattern whose constant
        is equal to the length of the nested pattern list.)

        Matching a target to `String[L] { P0, .., Pn }` means

            x instanceof String[] arr
                && arr.length matches L
                && arr.length >= n
                && arr[0] matches P0
                && arr[1] matches P1
                ...
                && arr[n] matches Pn

        More examples:

            case String[int len] { P }

        would match string arrays of length >= 1 whose first element
        matches P, and further binds the array length to `len`.

            case String[_] { P, Q }

        would match string arrays of any length whose first two
        elements match P and Q.

            case String[3] { }
                        ^constant pattern

        matches all string arrays of length 3.


        This is a more principled way to do it, because the length is
        a part of the array and deserves a chance to match via nested
        patterns, just as with the elements, and it avoid trying to
        give "..." a new meaning.

        The downside is that it might be confusing at first (though
        people will learn quickly enough) how to distinguish between
        an exact match and a prefix match.




        On 1/5/2021 1:48 PM, Brian Goetz wrote:

            As we get into the next round of pattern matching, I'd
            like to opportunistically attach another sub-feature:
            array patterns.  (This also bears on the question of "how
            would varargs patterns work", which I'll address below,
            though they might come later.)

            ## Array Patterns

            If we want to create a new array, we do so with an array
            construction expression:

                new String[] { "a", "b" }

            Since each form of aggregation should have its dual in
            destructuring, the natural way to represent an array
            pattern (h/t to AlanM for suggesting this) is:

                if (arr instanceof String[] { var a, var b }) { ... }

            Here, the applicability test is: "are you an instanceof of
            String[], with length = 2", and if so, we cast to
            String[], extract the two elements, and match them to the
            nested patterns `var a` and `var b`.   This is the natural
            analogue of deconstruction patterns for arrays, complete
            with nesting.

            Since an array can have more elements, we likely need a
            way to say "length >= 2" rather than simply "length ==
            2".  There are multiple syntactic ways to get there, for
            now I'm going to write

                if (arr instanceof String[] { var a, var b, ... })

            to indicate "more".  The "..." matches zero or more
            elements and binds nothing.

            <digression>
            People are immediately going to ask "can I bind something
            to the remainder"; I think this is mostly an "attractive
            distraction", and would prefer to not have this dominate
            the discussion.
            </digression>

            Here's an example from the JDK that could use this
            effectively:

            String[] limits = limitString.split(":");
            try {
                switch (limits.length) {
                    case 2: {
                        if (!limits[1].equals("*"))
            setMultilineLimit(MultilineLimit.DEPTH,
            Integer.parseInt(limits[1]));
                    }
                    case 1: {
                        if (!limits[0].equals("*"))
            setMultilineLimit(MultilineLimit.LENGTH,
            Integer.parseInt(limits[0]));
                    }
                }
            }
            catch(NumberFormatException ex) {
                setMultilineLimit(MultilineLimit.DEPTH, -1);
                setMultilineLimit(MultilineLimit.LENGTH, -1);
            }

            becomes (eventually)

                switch (limitString.split(":")) {
                    case String[] { var _, Integer.parseInt(var i) }
            -> setMultilineLimit(DEPTH, i);
                    case String[] { Integer.parseInt(var i) } ->
            setMultilineLimit(LENGTH, i);
                    default -> { setMultilineLimit(DEPTH, -1);
            setMultilineLimit(LENGTH, -1); }
                }

            Note how not only does this become more compact, but the
            unchecked "NumberFormatException" is folded into the
            match, rather than being a separate concern.


            ## Varargs patterns

            Having array patterns offers us a natural way to interpret
            deconstruction patterns for varargs records.  Assume we have:

                void m(X... xs) { }

            Then a varargs invocation

                m(a, b, c)

            is really sugar for

                m(new X[] { a, b, c })

            So the dual of a varargs invocation, a varargs match, is
            really a match to an array pattern.  So for a record

                record R(X... xs) { }

            a varargs match:

                case R(var a, var b, var c):

            is really sugar for an array match:

                case R(X[] { var a, var b, var c }):

            And similarly, we can use our "more arity" indicator:

                case R(var a, var b, var c, ...):

            to indicate that there are at least three elements.





Reply via email to