Tcl Part I · Module 6 · Lists
lset: Changing One Element in Place
This is the counterpart to lindex: where that one reads an element, this one writes it. It is the second of the two commands in this module that change your variable rather than handing back a new list, and it has one capability that makes it genuinely worth reaching for — it can update a single cell inside a nested structure without you having to pull the row out, change it, and put it back. It also behaves differently from lindex in one respect that will catch you if you assume otherwise.
Foundation10 min readTclPart IListsModificationNested
Part I · Module 6 · Chapter 6.8 · lset
1. The Command
set row {alpha beta gamma}
lset row 2 "updated"
puts $rowalpha beta updatedIt takes a variable name — no $ — a position, and a new value. The variable is changed directly.
That makes it one of only two list commands that write, the other being lappend. Everything else returns a new list — Chapter 6.7 §2 has the table.
2. Compared With lreplace
lreplace can do the same job and is clumsier for it:
set a {alpha beta gamma}
set b {alpha beta gamma}
lset a 2 "updated"
set b [lreplace $b 2 2 "updated"]
puts "lset: $a"
puts "lreplace: $b"lset: alpha beta updated
lreplace: alpha beta updatedSame result. lset says "change element 2" where lreplace says "replace the span from 2 to 2", which is a roundabout way to express the same intent — and it needs the assignment back.
Use lset for one element; lreplace for a span or a deletion.
3. Reaching Into Nested Lists
This is where it earns its place. Several indices address a cell inside a nested list:
set table {{clk 1} {reset 1} {payload 64}}
lset table 1 0 "rst_n"
puts $table{clk 1} {rst_n 1} {payload 64}One line changed the first element of the second row.
Without lset you would pull the row out, change it, and put it back:
set table {{clk 1} {reset 1} {payload 64}}
set row [lindex $table 1]
set row [lreplace $row 0 0 "rst_n"]
set table [lreplace $table 1 1 $row]
puts $table{clk 1} {rst_n 1} {payload 64}Three lines, a temporary variable, and two chances to get an index wrong. The indices read the same way as Chapter 6.4 §5's multi-index lindex — row first, then column.
4. Out of Range Is an Error
Here is where it differs from lindex:
set row {a b c}
puts [catch {lset row 9 "x"} message]
puts $message1
list index out of rangeAn error — not a silent no-op, and not an automatic extension of the list.
Compare with reading:
set row {a b c}
puts "lindex 9 -> <[lindex $row 9]>"
puts "lset 9 -> [catch {lset row 9 x}]"lindex 9 -> <>
lset 9 -> 1Reading past the end is silent; writing past the end is an error.
That asymmetry is the right design. An out-of-range read might legitimately mean "there is nothing there". An out-of-range write cannot mean anything sensible — there is no element to change — so Tcl refuses rather than guessing whether you wanted the list extended.
5. end Works
set row {a b c}
lset row end "last"
puts $row
lset row end-1 "middle"
puts $rowa b last
a middle lastThe same index vocabulary as everywhere else in the module.
6. It Returns the New List Too
set row {a b c}
puts "returns: [lset row 1 "X"]"
puts "row is: $row"returns: a X c
row is: a X cBoth modifies and returns, like lappend and incr. The return value is rarely used.
7. Updating a Table
The shape you will write:
set table {{clk 1 input} {reset 1 input} {payload 64 output}}
# widen the payload
lset table 2 1 128
# flip reset to active-low naming
lset table 1 0 "rst_n"
foreach row $table {
puts [format " %-10s %4s %s" {*}$row]
} clk 1 input
rst_n 1 input
payload 128 outputTwo updates, two lines, no temporaries. The {*}$row in the format call is Chapter 6.13, spreading the row's three elements into three arguments.
8. Common Mistakes
Writing lset $row 1 x. The name goes in without a $.
Expecting it to extend the list. Out of range is an error. Use lappend. §4.
Expecting silence like lindex. Writing is stricter than reading. §4.
Using lreplace for a single element. lset is clearer. §2.
Pulling a row out to change one cell. Use several indices. §3.
Using a deeply indexed lset on data that should be a dictionary. §7.
9. Exercises
Exercise 1 — change one. Set a three-element list, change the middle element with lset, and print before and after.
Exercise 2 — lset versus lreplace. Do the same change both ways. Which needed an assignment, and why?
Exercise 3 — a nested cell. For {{a 1} {b 2} {c 3}}, change the 2 to 20 with one lset. Then read it back with lindex using the same indices.
Exercise 4 — the asymmetry. On a three-element list, read index 9 and then try to write index 9. Explain why one is silent and the other is not.
Exercise 5 — end. Change the last and second-to-last elements of a four-element list without using llength.
Exercise 6 — extend or change. You have a three-element list and want a fourth element. Say why lset cannot do it and write the line that can.
10. Remember
lset varname index valuechanges one element in place. No$on the name.- It is one of only two list commands that modify the variable — the other is
lappend. - Several indices reach into nested lists:
lset table 1 0 value. - Those are the same indices
lindexwould use to read the cell. - Out of range is an error, unlike
lindex, which returns an empty string. - It cannot extend a list. Use
lappendto add. endandend-Nwork.- Prefer it to
lreplacefor a single element; preferlreplacefor spans and deletions.
Next: finding an element rather than addressing one.
Where this fits
Part of the Tcl curriculum.
