Author: Eric Taylor <et3@rocketship1.biz>
Author: Brian Griffin <briang42@easystreet.net>
State: Final
Type: Project
Vote: Done
Created: 28-06-2022
Tcl-Version: 8.7
Tcl-Branch: tip-629
Vote-Summary: Accepted 8/0/0
Votes-For: SL, MC, KK, AK, JN, FV, KW, BG
Votes-Against: none
Votes-Present: none
Abstract
This TIP proposes that a new list command, lseq
(formally
"range
"), be added to the core. The command would be similar to the
lrepeat
command, in that it would conveniently produce a list, given
some arguments. The command would take a range of rational numbers and
produce a list. It would be most useful in a foreach loop, but could
have other uses as well, such as when using the in
or ni
operators
in an expression or if
command.
Rationale and Discussion
Often one wants to iterate on a list of numbers, and the current most
popular choices are using a foreach
or for
command. The for
command is a throwback to the C for
statement, and is somewhat ugly
and can be difficult to read. It is also prone to off-by-one errors.
For ease of programming, especially when performance is not that
important, I propose to add to the core a command, lseq
, which
would provide a utility that is often found in other languages as a
keyword or built-in function. For example, Python permits a range of
numbers to be entered easily and has syntax to iterate over all the
values in a range.
Proposal
The lseq
command would take the following forms:
lseq Start ?(.. | to)? End ??by? Step?
lseq Start count Count ??by? Step?
lseq Count ?by Step?
The "..
" and the words "to
", "count
" and "by
" would be filler
words to make the command more readable. These are optional, similar
to how the word "else
" is optional in an "if
" statement.
If all the numeric values are whole numbers, then the range will
contain only integer values, otherwise values will be floating point
numbers. Numeric values may also be expressed as a valid expr
expression, for example: {$start+5}
. Normal Tcl quoting rules apply.
The most obvious use might be in a foreach loop. Instead of this to write the numbers 1 through 10,
for {set i 1} {$i <= 10} {incr i} {
puts $i
}
one could instead write:
foreach i [lseq 1 .. 10] {
puts $i
}
This would likely reduce the possibility of a programming error, where the programmer used < instead of <=.
It could also be used in an if statement, like so:
if {$i in [lseq 2 .. 10]} {puts inside} else {puts outside}
The command would understand when to create a list of numbers that are
decreasing, by the "start" and "end" values or by
using the optional "by
" and "step" arguments.
The "count
" operator would indicate the desire to create N elements, starting
at some value, with an optional increment (either positive or negative,
with a default of 1).
Examples
% lseq 10 .. 1
-> 10 9 8 7 6 5 4 3 2 1
% lseq 1 .. 10
-> 1 2 3 4 5 6 7 8 9 10
% lseq 10 .. 1 by 2 ;# wrong direction results in empty list
->
% lseq 10 .. 1 by -2
-> 10 8 6 4 2
% lseq 5.0 to 15.
-> 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0
% lseq 5.0 to 25. by 5
-> 5.0 10.0 15.0 20.0 25.0
% lseq 25. to 5. by -2.5
-> 25.0 22.5 20.0 17.5 15.0 12.5 10.0 7.5 5.0
% lseq 25. to 5. by -5
-> 25.0 20.0 15.0 10.0 5.0
% set start 1
% lseq $start to "$start+9" by 2
-> 1 3 5 7 9
% lseq 1 to 10 by -2 ;# wrong direction results in empty list
->
% lseq 25. to -25. by -3.25
-> 25.0 21.75 18.5 15.25 12.0 8.75 5.5 2.25 -1.0 -4.25 -7.5 -10.75 -14.0 -17.25 -20.5 -23.75
% lseq 5
-> 0 1 2 3 4
% lseq 3 count 7
-> 3 4 5 6 7 8 9
% lseq 0 count 8 by 2
-> 0 2 4 6 8 10 12 14
% lseq 10 0 -1.125
-> 10.0 8.875 7.75 6.625 5.5 4.375 3.25 2.125 1.0
Implementation
Each value in the sequence are computed via this equation where index is an integer value 0 through length-1:
value = start + (step * index)
The length of the list is determined by the Count value, or by the equation:
length = (end - start + step)/step
In the case where Step <= 0, the length will be 0 which means the result is an empty list.
The following is a pure Tcl prototype of the command. This is simplified by not including argument processing, a straight forward operation, but very lengthy.
Prototype to demonstrate the algorithm:
proc lseq {args} {
# magic processing $args here
processArguments $args ;# result in "start", "end", "step", and "count".
if {![info exists count]} {
set length [expr {($end-$start+$step)/$step}]
} else {
set length $count
}
# Create list
set value [list]
if {$length > 0} {
for {set index 0} {$i<$length} {incr index} {
lappend value [expr {$start + {$step * $index}}]
}
}
return $value
}
The complete C implementation is on the fossil branch tip-629.
Compatibility
Adding any command to the core would risk the possibility that some program might have chosen to write a proc using the new name. However, for new programs, the programmer would likely accept this limitation.
Discussion
I have attempted to take all the discussion comments into consideration. Concerns over inclusion vs exclusion of the "end" value in the sequence results have been addressed by having the resulting list contents based on 2 mathematical equations. Knowing these equations, the user can select parameters easily enough to get the needed results. Also, by using the straight line equation, error accumulation with float numbers is eliminated, or at least minimized.
Of all the proposed names for the command, I chose "lseq" because it is an 'l' command as are all the other List commands. I also thought that "seq", short for "sequence", would be more appropriate in the event that new options are proposed for the command to create sequences of numbers that may not be a linear range, for example: "lseq -fibonacci 0 to 100".
Honorable Mention
The implemented algorithm came from TIP-225, authored by Salvatore Sanfilippo and Miguel Sofer.
Copyright
This document has been placed in the public domain.