TIP 629: Add a lseq (formally "range") command to the core of list commands

Login
    Author:        Eric Taylor <[email protected]>
    Author:        Brian Griffin <[email protected]>
    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.