Updated 2011-05-18 06:27:12 by aspect

lshift - shift list's elements, removing the first element and returning it as the value of the function
lshift listVar

This is not a command included in Tcl, but rather a common 'idiom' for in-place list manipulation used e.g. for implementing queues are for command line processing.

A proper implementation should throw an error when listVar does not exist, and also when listVar contains the empty list (or string).

Implementations  edit

The following is taken from Tcl Gems though slightly modified:
proc lshift listVar {
    upvar 1 $listVar l
    set r [lindex $l 0]
    set l [lreplace $l [set l 0] 0]
    return $r
}

You can use this programming idiom also without having the lshift command, e.g. with the K combinator:
    K [lindex $l 0] [set l [lreplace $l [set l 0] 0]

Or more generalized:
lshift listVar ?count?

Shift list count elements and return the respective elements.
proc lshift {listVar {count 1}} {
    upvar 1 $listVar l
    set r [lrange $l 0 [incr count -1]]
    set l [lreplace $l [set l 0] $count]
    return $r
}

And finally a full fledged one with error handling:
proc lshift {listVar {count 1}} {
    upvar 1 $listVar l
    if {![info exists l]} {
        # make the error message show the real variable name
        error "can't read \"$listVar\": no such variable"
    }
    if {![llength $l]} {error Empty}
    set r [lrange $l 0 [incr count -1]]
    set l [lreplace $l [set l 0] $count]
    return $r
}

fredderic The two versions of this command offered above return non-simple list elements differently. One returns the first item, the second returns the first portion of the list. So I'm offering up the version I wrote for my own toolkit some time ago:
proc lshift {args} {
    lassign $args listVar count
    upvar 1 $listVar var
    if { ! [info exists var] } {
        return -level 1 -code error \
            "can't read \"$listVar\": no such variable"
    }
    switch -exact -- [llength $args] {
    1 {
            set var [lassign $var value]
        }
    2 {
            set value [lrange $var 0 $count-1]
            set var [lrange $var $count end]
        }
    default {
            return -level 1 -code error \
                "wrong # args: should be \"lshift listVar ?count?\""
            # error-args "lshift listVar ?count?"
        }
    }
    return $value
}

The key here, is that if you don't give an item count, then it returns the first item off the list as a discrete value. If you give a count, then it returns that many items off the front as a sub-list. Therefore, lshift list performs as the K variant, while lshift list 1 performs as in the second variant shown.

aspect -- elaborating on fredderic's comment above, the problem he mentions won't actually strike (except perhaps in terms of performance -- shimmering?) until you try something like:
% lshift {1 # 2 3}
1
% lshift {# 2 3}
{#}

The result here is a valid one-element list containing {#}, but the naive programmer might expect lshift with one argument to return an element rather than a one-element list (in conformance with the behaviour of ::struct::list shift). The moral of the story is to trust obviously correct code, and mistrust puns like [lrange $l 0 0] == [lindex $l 0] (which is "mostly true", if you ask expr).

Different Interpretations  edit

In lshift -Adding Unique Items to Lists of Fixed Length the command lshift puts an element on a list if it is not already there. It is used to implement a 'recently used' list

Nifty Tricks  edit

In Tcl Gems lshift is used for command line arguments processing. In Stacks and Queues the genial RS names lshift qpop - if push appends to the end, then pop pops from the end (stack) and lshift (qpop) pops from the front (queue).

LEG would like to contribute the following method to iterate over lines of text, whether stored in a file or in a variable:
proc vGets {listVar lineVar} {
    upvar 1 $listVar l $lineVar line
    if {[catch {lshift l} line]} {return -1} else {string length $line}
}
if {..} {# text in variable "text"
    set lines [split $text \n]
    set getLine vGets
} else {# text in channel fd
    set lines $fd
    set getLine gets
}
while {[$getLine lines line]!=-1} {
   # do something with the line ..
}

LV note that within tcllib's struct module there is a "struct::list shift" command. Perhaps some of the additional functions recently written on the wiki might fit into the same module.