This package provides additional ways to create a range in Julia. This package has no dependencies other than the Julia standard library.
In doing so, it commits type piracy by extending the method
range to act on types not defined in this package. You are encouraged to examine the implementation of this package so you do not actually
need to use it.
A range is a sequence of numbers or other items that have a start and a stop. Here we focus on arithmetic linear ranges which are actually
arithmetic sequences starting at start, incremented by a step and ending at or before stop. The number of elements in a range
constitutes its length.
Ranges in Julia often start at 1 by default due to the one-based nature of the language in terms of array indexing
They are often inclusive of their endpoint at stop if reachable by incrementing by step. In particular, this differs
from a language such as Python where ranges start at 0 and tend not to include the upper bound.
The additional forms added here use the following principles:
- Provide complementary syntax to
start:stopandstart:step:stop. That is if you can do it well with the colon syntax, a redundantrangemethod is not added here. Ratherrangeallows you to construct a sequence not easily done with colon. - Allow for maximum user friendliness, which comes at some cost of sustainable software development.
Because of item 1 above, the emphasis here is on providing additional syntax for length since step is well covered by the colon syntax.
The internal design of the package is meant to allow you to copy selected lines if needed or demonstrate how to create a certain kind of range with limited arguments using Base Julia.
This package adds several positional forms of range:
range(length) # `1:length`, actually it is `Base.OneTo(length)`
range(start, length) # `start:start+length-1`, or `range(start; length)`
range(start, stop, length) # range(start, stop; length)
range(start, step, stop, length) # Base._range, one arg must be nothingIn the positional syntax, length is always the last argument. It is only argument in the one argument form.
start is always the first argument when there are two or more arguments.
The single argument form mimicks the Python version, except that it starts from 1 and ends with the length argument given.
The two argument form allow for start and length to be provided. The reason range(start,stop) is not implemented is that this
is easily handled by the colon syntax in base Julia: start:stop.
The three argument form allows for start, stop, and length to be specified as positional arguments.
The four argument form allows for start, step, stop, and length to be specified as positional arguments. One of the arguments must be nothing.
step is not used here because it can be specified by start:step:stop in base Julia.
Usage:
julia> using Ranges
julia> range(5)
Base.OneTo(5)
julia> range(2, 5)
2:6
julia> range(1, 10, 3)
1.0:4.5:10.0
julia> range(1, nothing, 10, 4)
1.0:3.0:10.0Do you find the order of the positional arguments confusing? I do as well, but some people like a pure positional syntax. A more logical approach might have been:
range(length, start=nothing, stop=nothing) # Not implemented, clashes with default Base.range order!
range(:lbe, length, start=nothing, start=nothing)Because the order of arguments may not be intuitive, start and stop may be provided as start => stop which is a Pair.
This allows length to be provided as either the first or last argument.
range(start => stop, length) # Same as range(start, stop; length)
range(length, start => stop) # Same as range(start, stop; length)
range(start => stop) # Produces a curried function similar to `(length=nothing; step=nothing) -> range(start; stop, length, step)`The last form returns another function that remembers the start and stop given in the Pair.
It takes a single positional argument of length. It can also optionally take a single keyword argument
of either step or length.
Usage:
julia> using Ranges
julia> range(1 => 5, 2)
1.0:4.0:5.0
julia> range(2, 1 => 5)
1.0:4.0:5.0
julia> range(3, 1 => 5)
1.0:2.0:5.0
julia> r = range(1 => 5)
Ranges.PartialRange(1, 5)
julia> r(3)
1.0:2.0:5.0
julia> r(step = 2)
1:2:5
julia> r(length = 3)
1.0:2.0:5.0If you would like to be clear about all parameters given for a range, all four prameters can be specified as keywords.
julia> using Ranges
julia> range(;length = 3, start = 1)
1:3
julia> range(;length = 3, start = 1, stop = 5)
1.0:2.0:5.0
julia> range(; start=3, step = 2, length=100)
3:2:201If you liked the flexiblity of the keyword form, but also found it more verbose, this package also provides a single letter abbreviation form
via the BELS system. BELS stands for [b]egin, [e]nd, [l]ength, and [s]tep, drawing inspiration from existing Julia concepts.
| Current | New | Example | New Abbreviation |
|---|---|---|---|
| start | begin | a[begin] | b |
| stop | end | a[end] | e |
| length | length | length(a) | l |
| step | step | step(a) | s |
Each of these forms involve providing a Symbol as the first argument. If that symbol is :bels, then b, e, l, and s may be provided as keywords.
If the symbol uses less than four letters, then the symbol denotes the order of positional arguments. For example, :bel indicates that the order of arguments
will be [b]egin, [e]nd, and [l]ength. If the symbol was :bes, then the order of arguments will be [b]egin, [e]nd, and [s]tep.
Effort is made to produce a range given any combination of arguments.
range(:bels; b, e, l, s)
range(:[bels], args...)Usage:
julia> using Ranges
julia> range(:bels, b=5, e=10, l=3)
5.0:2.5:10.0
julia> range(:bels, e=10, l=3, s=2)
6:2:10
julia> range(:be, 1, 10)
1:10
julia> range(:be, 2, 10)
2:10
julia> range(:bl, 2, 10)
2:11As of v0.2.0, symbols that start with :l like :lbe have default arguments. These defaults calculated from the given arguments or assume start = 1, step = 1.
Usage:
julia> using Ranges # v0.0.2
julia> range(:lbe, 5)
1:5
julia> range(:lbe, 5, 2)
2:6
julia> range(:lbe, 5, 2, 3)
2.0:0.25:3.0length(r::AbstractRange, len)
length(r::Pair, len)Usage:
julia> length(1:5, 11)
1.0:0.4:5.0
julia> length(1:5, 21)
1.0:0.2:5.0
julia> length(3.5 => 22.2, 11)
3.5:1.87:22.2step(r::AbstractRange, s)
step(r::Pair, s)Usage:
julia> step(1:5, 2)
1:2:5
julia> step(1:10, 2)
1:2:9
julia> step(1 => 5.5, 3)
1.0:3.0:4.0first(r::AbstractRange, f)Usage:
julia> first(1:5, 3)
3:1:5last(r::AbstractRange, l)Usage:
julia> last(1:5, 3)
1:1:3Let's review the distinct ways to create a range in base Julia. In general, ranges in Base
are iterators and use Julia's iteration protocol. To obtain the actually sequence a method such as collect can be used to obtain all the elements
of the iterator in order.
The colon syntax allows ranges to be created using start, stop, and optionally step.
The simplest colon syntax is start:stop where step defaults to 1. In this case start and stop are included in the iteration.
This produces a Julia struct of type UnitRange.
julia> 1:5
1:5
julia> print( collect( 1:5 ) )
[1, 2, 3, 4, 5]
julia> typeof(1:5)
UnitRange{Int64}A second colon syntax allows you to specify step: start:step:stop. If stop cannot be reached by incrementing by step from start,
stop may not be included in the iteration. This produces a Julia struct of type StepRange.
julia> 1:2:10
1:2:9
julia> print( collect( 1:2:10 ) )
[1, 3, 5, 7, 9]
julia> typeof(1:2:10)
StepRange{Int64,Int64}The method range comes in several forms. The first form takes a single positional argument, start, and accepts
stop, step, and/or length as keyword arguments. This can produce either a UnitRange, StepRange, or StepRangeLen.
The valid combinations of keyword arguments are below.
range(start; stop) # Redundant with `start:stop`
range(start; length)
range(start; step, length)If neither step or length is specified, step defaults to 1.
A combination of only start and step will produce an ArgumentError. Specifying all three keywords will also
produce an ArgumentError.
julia> range(1; stop = 5)
1:5
julia> typeof( range(1; stop = 5) )
UnitRange{Int64}
julia> range(1; stop = 5, length = 3)
1.0:2.0:5.0
julia> typeof( range(1; stop = 5, length = 3) )
StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}
julia> range(1; length = 3, step = 2)
1:2:5
julia> typeof( range(1; length = 3, step = 2) )
StepRange{Int64,Int64}
julia> range(2; length=3)
2:4
julia> range(2; step=3)
ERROR: ArgumentError: At least one of `length` or `stop` must be specified
julia> range(1; stop=5, length = 2, step = 4)
ERROR: ArgumentError: Too many arguments specified; try passing only one of `stop` or `length`A second form of range allows for two positional arguments, start and stop. Exactly one
additional keyword, step or length must be provided. The valid forms are below.
range(start, stop; step)
range(start, stop; length)It is not valid to provide two positional arguments only. It is also not valid to provide
both keyword arguments in this form. These will throw an ArgumentError.
julia> range(1, 5, step = 2)
1:2:5
julia> range(1, 5, length = 2)
1.0:4.0:5.0
julia> range(1, 5)
ERROR: ArgumentError: At least one of `length` or `step` must be specified
...
julia> range(1, 5, length = 2, step = 4)
ERROR: ArgumentError: Too many arguments specified; try passing only one of `stop` or `length`Julia's has some escoteric keyword passing conventions. For example, if you pass a variable after the semicolon, ;, the name of the variable can indicate the keyword.
julia> let length = 11, step = 3
range(1; length, step)
end
1:3:31You can also use a NamedTuple in the following way about a semicolon, ;:
julia> nt = (stop=3, length=11)
(stop = 3, length = 11)
julia> range(1; nt.length, nt.stop) # The property name indicates the keyword
1.0:0.2:3.0
julia> range(1; nt...) # Splatting
1.0:0.2:3.0The design of this package is such that each depends minimally on other lines of code in the package. This allows you to copy a single line out of this package into your code or REPL in order to use that specific syntax.
This also allows you to discover how to perform the action you want via the default range in Base Julia.
For example, you may really like range(start, stop, length).
To make only that syntax function, you can copy the following line of code to gain that functionality:
# Implements range(start, stop, length)
Base.range(start::Number, stop, length::Integer) = Base.range(start, stop; length) # from src/positional.jlAlternatively, you really like range(start => stop, length).
# Implements range(start => stop, length)
Base.range(r::Pair, length) = Base.range(r.first, r.second; length) # from src/pair.jlSome of the more complicated examples may require two lines of code to function.
For example range(:lbe, length, start, stop) needs the following.
# Implements range(:lbe, length, start, stop)
Base.range(s::Symbol, args... ; kwargs...) = Base.range(Val(s), args... ; kwargs...) # from src/keyword.jl
Base.range(::Val{:lbe}, l, b=1, e=nothing) = Base.range(b, e; length=l)This package is unrelated to https://github.com/JuliaArrays/Ranges.jl which is obsolete.