Skip to content

Commit bba2902

Browse files
committed
More docs around ranges
1 parent 09bf540 commit bba2902

File tree

4 files changed

+78
-17
lines changed

4 files changed

+78
-17
lines changed

lib/elixir/lib/enum.ex

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2601,11 +2601,14 @@ defmodule Enum do
26012601
iex> Enum.slice(1..30, -5..-1)
26022602
[26, 27, 28, 29, 30]
26032603
2604-
# last five elements (mixed positive and negative indexes)
2605-
iex> Enum.slice(1..30, 25..-1)
2604+
For ranges where `start > stop`, you need to explicit
2605+
mark them as increasing:
2606+
2607+
iex> Enum.slice(1..30, 25..-1//1)
26062608
[26, 27, 28, 29, 30]
26072609
2608-
# out of bounds
2610+
If values are out of bounds, it returns an empty list:
2611+
26092612
iex> Enum.slice(1..10, 11..20)
26102613
[]
26112614
@@ -2617,6 +2620,7 @@ defmodule Enum do
26172620
@doc since: "1.6.0"
26182621
@spec slice(t, Range.t()) :: list
26192622
def slice(enumerable, first..last//step = index_range) do
2623+
# TODO: Deprecate negative steps on Elixir v1.16
26202624
# TODO: There are two features we can add to slicing ranges:
26212625
# 1. We can allow the step to be any positive number
26222626
# 2. We can allow slice and reverse at the same time. However, we can't

lib/elixir/lib/kernel.ex

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3641,6 +3641,8 @@ defmodule Kernel do
36413641
to last, albeit this behaviour is deprecated. Instead prefer to
36423642
explicitly list the step with `first..last//-1`.
36433643
3644+
See the `Range` module for more information.
3645+
36443646
## Examples
36453647
36463648
iex> 0 in 1..3
@@ -3666,7 +3668,7 @@ defmodule Kernel do
36663668
end
36673669

36683670
defp range(_context, first, last) when is_integer(first) and is_integer(last) do
3669-
# TODO: Deprecate inferring a range with step of -1 on Elixir v1.16
3671+
# TODO: Deprecate inferring a range with step of -1 on Elixir v1.17
36703672
step = if first <= last, do: 1, else: -1
36713673
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last, step: step]}
36723674
end
@@ -3676,18 +3678,20 @@ defmodule Kernel do
36763678
end
36773679

36783680
defp range(:guard, first, last) do
3679-
# TODO: Deprecate me inside guard when sides are not integers on Elixir v1.16
3681+
# TODO: Deprecate me inside guard when sides are not integers on Elixir v1.17
36803682
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last, step: nil]}
36813683
end
36823684

36833685
defp range(:match, first, last) do
3684-
# TODO: Deprecate me inside match in all occasions (including literals) on Elixir v1.16
3686+
# TODO: Deprecate me inside match in all occasions (including literals) on Elixir v1.17
36853687
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
36863688
end
36873689

36883690
@doc """
36893691
Creates a range from `first` to `last` with `step`.
36903692
3693+
See the `Range` module for more information.
3694+
36913695
## Examples
36923696
36933697
iex> 0 in 1..3//1

lib/elixir/lib/range.ex

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
defmodule Range do
22
@moduledoc """
3-
Ranges represent a sequence of one or many, ascending
3+
Ranges represent a sequence of zero, one or many, ascending
44
or descending, consecutive integers.
55
6-
Ranges are always inclusive and they may have custom
7-
steps. The most common form of creating and matching
8-
on ranges is via the `../2` and `..///3` macros,
6+
Ranges are always inclusive and they may have custom steps.
7+
The most common form of creating and matching on ranges is
8+
via the `start..stop` and `start..stop//step` notations,
9+
defined respectively as the `../2` and `..///3` macros
910
auto-imported from `Kernel`:
1011
1112
iex> Enum.to_list(1..3)
@@ -15,6 +16,44 @@ defmodule Range do
1516
iex> Enum.to_list(3..1//-1)
1617
[3, 2, 1]
1718
19+
Ranges may also have a single element:
20+
21+
iex> Enum.to_list(1..1)
22+
[1]
23+
iex> Enum.to_list(1..1//2)
24+
[1]
25+
26+
Or even no elements at all:
27+
28+
iex> Enum.to_list(10..0//1)
29+
[]
30+
iex> Enum.to_list(0..10//-1)
31+
[]
32+
33+
When defining a range without steps, the step will be
34+
defined based on the start and stop position of the
35+
range, If `start >= stop`, it will be an increasing range
36+
with step of 1. Otherwise, it is a decreasing range.
37+
Note however implicitly decreasing ranges are deprecated.
38+
Therefore, if you need a decreasing range from `3` to `1`,
39+
prefer to write `3..1//-1` instead.
40+
41+
## Definition
42+
43+
An increasing range `first..last//step` is a range from
44+
`first` to `last` increasing by `step` where all values
45+
`v` must be `first <= v and v <= last`. Therefore, a range
46+
`10..0//1` is an empty range because there is no value `v`
47+
that is `10 <= v and v <= 0`.
48+
49+
Similarly, a decreasing range `first..last//-step` is a range
50+
from `first` to `last` decreasing by `step` where all values
51+
`v` must be `first >= v and v >= last`. Therefore, a range
52+
`0..10//-1` is an empty range because there is no value `v`
53+
that is `10 >= v and v >= 0`.
54+
55+
## Representation
56+
1857
Internally, ranges are represented as structs:
1958
2059
iex> range = 1..9//2
@@ -62,6 +101,14 @@ defmodule Range do
62101
@doc """
63102
Creates a new range.
64103
104+
If first is less than last, the range will be increasing from
105+
first to last. If first is equal to last, the range will contain
106+
one element, which is the number itself.
107+
108+
If first is more than last, the range will be decreasing from first
109+
to last, albeit this behaviour is deprecated. Instead prefer to
110+
explicitly list the step `new/3`.
111+
65112
## Examples
66113
67114
iex> Range.new(-100, 100)
@@ -70,7 +117,7 @@ defmodule Range do
70117
"""
71118
@spec new(integer, integer) :: t
72119
def new(first, last) when is_integer(first) and is_integer(last) do
73-
# TODO: Deprecate inferring a range with step of -1 on Elixir v1.16
120+
# TODO: Deprecate inferring a range with step of -1 on Elixir v1.17
74121
step = if first <= last, do: 1, else: -1
75122
%Range{first: first, last: last, step: step}
76123
end

lib/elixir/lib/string.ex

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2052,19 +2052,24 @@ defmodule String do
20522052
iex> String.slice("elixir", 1..10)
20532053
"lixir"
20542054
2055-
iex> String.slice("elixir", 10..3)
2056-
""
2057-
20582055
iex> String.slice("elixir", -4..-1)
20592056
"ixir"
20602057
2061-
iex> String.slice("elixir", 2..-1)
2058+
iex> String.slice("elixir", -4..6)
20622059
"ixir"
20632060
2064-
iex> String.slice("elixir", -4..6)
2061+
For ranges where `start > stop`, you need to explicit
2062+
mark them as increasing:
2063+
2064+
iex> String.slice("elixir", 2..-1//1)
20652065
"ixir"
20662066
2067-
iex> String.slice("elixir", -1..-4)
2067+
iex> String.slice("elixir", 1..-2//1)
2068+
"lixi"
2069+
2070+
If values are out of bounds, it returns an empty string:
2071+
2072+
iex> String.slice("elixir", 10..3)
20682073
""
20692074
20702075
iex> String.slice("elixir", -10..-7)
@@ -2079,6 +2084,7 @@ defmodule String do
20792084
"""
20802085
@spec slice(t, Range.t()) :: t
20812086
def slice(string, first..last//step = range) when is_binary(string) do
2087+
# TODO: Deprecate negative steps on Elixir v1.16
20822088
# TODO: There are two features we can add to slicing ranges:
20832089
# 1. We can allow the step to be any positive number
20842090
# 2. We can allow slice and reverse at the same time. However, we can't

0 commit comments

Comments
 (0)