=begin pod

=TITLE class Range

=SUBTITLE Interval of ordered values

=for code :skip-test
class Range is Iterable does Positional {}

Ranges serve two main purposes: to generate lists of consecutive
numbers or strings, and to act as a matcher to check if a number
or string is within a certain range.

Ranges are constructed using one of the four possible range operators,
which consist of two dots, and optionally a caret which indicates that
the endpoint marked with it is excluded from the range.

    1 .. 5;  # 1 <= $x <= 5
    1^.. 5;  # 1 <  $x <= 5
    1 ..^5;  # 1 <= $x <  5
    1^..^5;  # 1 <  $x <  5

The caret is also a prefix operator for constructing numeric ranges
starting from zero:

    my $x = 10;
    say ^$x;     # same as 0 ..^ $x.Numeric

Iterating a range (or calling the C<list> method) uses the same semantics as
the C<++> prefix and postfix operators, i.e., it calls the C<succ> method on
the start point, and then the generated elements.

Ranges always go from small to larger elements; if the start point is bigger
than the end point, the range is considered empty.

    for 1..5 { .say };       # OUTPUT: «1␤2␤3␤4␤5␤»
    ('a' ^..^ 'f').list;     # RESULT: «'b', 'c', 'd', 'e'»
    5 ~~ ^5;                 # RESULT: «False»
    4.5 ~~ 0..^5;            # RESULT: «True»
    (1.1..5).list;           # RESULT: «(1.1, 2.1, 3.1, 4.1)»

Use the L<...|/language/operators#infix_...> sequence operator to produce lists of elements that
go from larger to smaller values, or to use offsets other than
increment-by-1 and other complex cases.

Use C<Inf> or C<*> (Whatever) to indicate an end point to be open-ended.

=for code :skip-test
for 1..* { .say };       # start from 1, continue until stopped
for 1..Inf { .say };     # the same

=head2 Ranges in subscripts

A Range can be used in a subscript to get a range of values. Please note that
assigning a Range to a scalar container turns the Range into a item. Use
binding, @-sigiled containers or a slip to get what you mean.

    my @numbers =  <4 8 15 16 23 42>;
    my $range := 0..2;
    .say for @numbers[$range]; # OUTPUT: «4␤8␤15␤»
    my @range = 0..2;
    .say for @numbers[@range]; # OUTPUT: «4␤8␤15␤»


=head1 Methods

=head2 method min

    method min(Range:D:)

Returns the start point of the range.

    say (1..5).min;                                   # OUTPUT: «1␤»
    say (1^..^5).min;                                 # OUTPUT: «1␤»

=head2 method excludes-min

    method excludes-min(Range:D: --> Bool:D)

Returns C<True> if the start point is excluded from the range, and C<False>
otherwise.

    say (1..5).excludes-min;                          # OUTPUT: «False␤»
    say (1^..^5).excludes-min;                        # OUTPUT: «True␤»

=head2 method max

    method max(Range:D:)

Returns the end point of the range.

    say (1..5).max;                                   # OUTPUT: «5␤»
    say (1^..^5).max;                                 # OUTPUT: «5␤»

=head2 method excludes-max

    method excludes-max(Range:D: --> Bool:D)

Returns C<True> if the end point is excluded from the range, and C<False>
otherwise.

    say (1..5).excludes-max;                          # OUTPUT: «False␤»
    say (1^..^5).excludes-max;                        # OUTPUT: «True␤»

=head2 method bounds

    method bounds(Range:D: --> Positional)

Returns a list consisting of the start and end point.

    say (1..5).bounds;                                # OUTPUT: «(1 5)␤»
    say (1^..^5).bounds;                              # OUTPUT: «(1 5)␤»

=head2 method infinite

    method infinite(Range:D: --> Bool:D)

Returns C<True> if either end point was declared with C<Inf> or C<*>.

    say (1..5).infinite;                              # OUTPUT: «False␤»
    say (1..*).infinite;                              # OUTPUT: «True␤»

=head2 method is-int

    method is-int(Range:D: --> Bool:D)

Returns C<True> if both end points are C<Int> values.

    say ('a'..'d').is-int;                            # OUTPUT: «False␤»
    say (1..^5).is-int;                               # OUTPUT: «True␤»
    say (1.1..5.5).is-int;                            # OUTPUT: «False␤»

=head2 method int-bounds

    method bounds(Range:D: --> Positional)

If the C<Range> is an integer range (as indicated by L<is-int>), then this
method returns a list with the first and last value it will iterate over
(taking into account L<excludes-min> and L<excludes-max>).  Returns a
Failure if it is not an integer range.


=head2 method minmax

Defined as:

    multi method minmax(Range:D: --> List:D)

If the C<Range> is an integer range (as indicated by L<is-int>), then this
method returns a list with the first and last value it will iterate over
(taking into account L<excludes-min> and L<excludes-max>). If the range is
not an integer range, the method will return a two element list containing
the start and end point of the range unless either of L<excludes-min> or
L<excludes-max> are C<True> in which case a L<Failure|/type/Failure> is returned.

    my $r1 = (1..5); my $r2 = (1^..5);
    say $r1.is-int, ', ', $r2.is-int;                 # OUTPUT: «True, True␤»
    say $r1.excludes-min, ', ', $r2.excludes-min;     # OUTPUT: «False, True␤»
    say $r1.minmax, ', ', $r2.minmax;                 # OUTPUT: «(1 5), (2 5)␤»

    my $r3 = (1.1..5.2); my $r4 = (1.1..^5.2);
    say $r3.is-int, ', ', $r4.is-int;                 # OUTPUT: «False, False␤»
    say $r3.excludes-max, ', ', $r4.excludes-max;     # OUTPUT: «False, True␤»
    say $r3.minmax;                                   # OUTPUT: «(1.1 5.2)␤»
    say $r4.minmax;
    CATCH { default { put .^name, ': ', .Str } };
    # OUTPUT: «X::AdHoc: Cannot return minmax on Range with excluded ends␤»

=head2 method elems

    method elems(Range:D: --> Numeric:D)

Returns the number of elements in the range, e.g. when being iterated over,
or when used as a C<List>.  Returns Inf if either end point was specified
as C<Inf> or C<*>.

    say (1..5).elems;                                 # OUTPUT: «5␤»
    say (1^..^5).elems;                               # OUTPUT: «3␤»

=head2 method list

    method list(Range:D: --> List:D)

Generates the list of elements that the range represents.

    say (1..5).list;                                  # OUTPUT: «(1 2 3 4 5)␤»
    say (1^..^5).list;                                # OUTPUT: «(2 3 4)␤»

=head2 method flat

    method flat(Range:D: --> List:D)

Generates the list of elements that the range represents.

=head2 method pick

    multi method pick(Range:D:         --> Any:D)
    multi method pick(Range:D: $number --> Seq:D)

Performs the same function as C<Range.list.pick>, but attempts to optimize
by not actually generating the list if it is not necessary.

=head2 method roll

    multi method roll(Range:D:         --> Any:D)
    multi method roll(Range:D: $number --> Seq:D)

Performs the same function as C<Range.list.roll>, but attempts to optimize
by not actually generating the list if it is not necessary.

=head2 method sum

    multi method sum(--> Numeric:D)

Returns the sum of all elements in the Range. Throws L<X::Str::Numeric> if an element can not be coerced into Numeric.

    (1..10).sum                                       # 55

=head2 method reverse

    method reverse(Range:D: --> Seq:D)

Returns a L<Seq> where all elements that the C<Range> represents have been
reversed. Note that reversing an infinite C<Range> won't produce any meaningful
results.

    say (1^..5).reverse;                              # OUTPUT: «(5 4 3 2)␤»
    say ('a'..'d').reverse;                           # OUTPUT: «(d c b a)␤»
    say (1..Inf).reverse;                             # OUTPUT: «(Inf Inf Inf ...)␤»

=end pod
