Skip to content
VLSI Mentor

Tcl Part I · Module 4 · Operators and Expressions

in and ni: Membership Inside an Expression

A condition that asks whether a value is one of several accepted options comes up constantly — checking a mode, validating a user-supplied option, deciding whether a status counts as a failure. Tcl has two operators for exactly this, and they produce a truth value directly rather than an index you have to compare against minus one. They also make a decision you need to know about: the comparison is textual, which is the right default for the things people usually test and the wrong one for numbers.

Foundation10 min readTclPart IOperatorsMembershipValidation

Part I · Module 4 · Chapter 4.9 · in and ni: Membership Inside an Expression

1. The Two Operators

Azvya Education Pvt. Ltd.VLSI Mentor
basic.tcl
set mode fast

puts "mode in  {fast slow} = [expr {$mode in {fast slow}}]"
puts "mode ni  {fast slow} = [expr {$mode ni {fast slow}}]"
puts "bogus ni {fast slow} = [expr {"bogus" ni {fast slow}}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mode in  {fast slow} = 1
mode ni  {fast slow} = 0
bogus ni {fast slow} = 1

in is "is an element of". ni is "is not an element of" — it is simply the negation, provided so you do not have to write !(... in ...).

The right-hand side is a list. Here it is a braced literal, which Chapter 2.8 showed arrives as one word. It can equally be a variable:

Azvya Education Pvt. Ltd.VLSI Mentor
variable-list.tcl
set supported {fast slow careful}
set mode careful

puts "supported? [expr {$mode in $supported}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
supported? 1

2. Whole Elements Only

The match is against complete elements, never parts of them:

Azvya Education Pvt. Ltd.VLSI Mentor
whole.tcl
puts "\"fast\" in {fast slow} = [expr {"fast" in {fast slow}}]"
puts "\"fa\"   in {fast slow} = [expr {"fa" in {fast slow}}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
"fast" in {fast slow} = 1
"fa"   in {fast slow} = 0

fa is not an element, even though it is the start of one. If you want partial matching you want string match or a regular expression, which are Modules 5 and 13.

3. The Comparison Is Textual

This is the thing to know, and it follows the pattern of the whole module:

Azvya Education Pvt. Ltd.VLSI Mentor
textual.tcl
puts "1     in {1.0 2} = [expr {1 in {1.0 2}}]"
puts "1.0   in {1.0 2} = [expr {1.0 in {1.0 2}}]"
puts "10    in {9 10}  = [expr {10 in {9 10}}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1     in {1.0 2} = 0
1.0   in {1.0 2} = 1
10    in {9 10}  = 1

The first line is false. 1 and 1.0 are the same number and different text, and in compares text — exactly like eq in Chapter 4.4.

The second line is true because the characters match exactly. The third is true for the same reason: 10 is written identically in both places.

So membership succeeds only when the value is written the same way as the element.

4. Where It Earns Its Place

Validating an option:

Azvya Education Pvt. Ltd.VLSI Mentor
validate.tcl
set supported {fast slow careful}

foreach mode {fast turbo careful} {
    if {$mode ni $supported} {
        puts "unknown mode: $mode"
    } else {
        puts "running in $mode mode"
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
running in fast mode
unknown mode: turbo
running in careful mode

Classifying a status:

Azvya Education Pvt. Ltd.VLSI Mentor
status.tcl
foreach status {PASS FAIL SKIP ERROR} {
    set bad [expr {$status in {FAIL ERROR TIMEOUT}}]
    puts [format "%-6s counts as failure: %s" $status $bad]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PASS   counts as failure: 0
FAIL   counts as failure: 1
SKIP   counts as failure: 0
ERROR  counts as failure: 1

The accepted set is written once, in one place, as a list. Adding TIMEOUT to the failure set is a one-word change rather than another || clause.

5. Compared With lsearch

lsearch (Module 6) can answer the same question, and it is worth seeing why in is better inside a condition:

Azvya Education Pvt. Ltd.VLSI Mentor
lsearch.tcl
set supported {fast slow careful}
set mode careful

puts "lsearch returns: [lsearch -exact $supported $mode]"
puts "as a condition:  [expr {[lsearch -exact $supported $mode] != -1}]"
puts "with in:         [expr {$mode in $supported}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
lsearch returns: 2
as a condition:  1
with in:         1

lsearch returns an index, and -1 when there is no match. To use it in a condition you have to wrap it in a comparison against -1, which is noise — and it is easy to get wrong, because 0 is a perfectly good index and is also false.

in returns a truth value directly. Use lsearch when you want to know where something is; use in when you want to know whether it is there.

6. The Alternative for Many Cases

When the list grows and each case needs its own action, in is not the right shape any more:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {$mode in {fast turbo quick}} {
    ...
}

That is fine for one group. For several groups each doing something different, switch in Module 8 gives each case its own branch and reads far better than a chain of in tests.

7. Common Mistakes

Expecting numeric membership. §3. 1 in {1.0} is false.

Expecting partial matches. §2. fa is not in {fast}.

Writing !(... in ...). Use ni.

Using lsearch in a condition without != -1. An index of 0 is a match and is also false.

Forgetting the right side must be a valid list. A string that is not a well-formed list will error or behave unexpectedly — Module 6 covers what makes a list well-formed.

Chaining several in tests for different actions. Use switch.

8. Exercises

Exercise 1 — basic membership. Set a variable to slow and test it against {fast slow careful} with both in and ni. Then test turbo the same way.

Exercise 2 — whole elements. Predict and check: "car" in {carpet cart}, "cart" in {carpet cart}, "carpet cart" in {carpet cart}. Explain the third.

Exercise 3 — the numeric trap. Predict expr {5 in {5.0 6}} and expr {5 in {5 6}}. Then write a numeric membership test for 5 that works whichever way the list is written.

Exercise 4 — a validator. Write a loop over the modes quick, full, debug and turbo that prints ok or unknown mode: X, with the accepted set defined once as a variable.

Exercise 5 — replace lsearch. Rewrite if {[lsearch -exact $list $x] != -1} { ... } using in, and say what bug the original form invites.

Exercise 6 — failure statuses. Build a list of statuses that count as failures, then classify PASS, FAIL, WARN and ERROR against it. Add WARN to the set and confirm only one line changed.

9. Remember

  • in tests membership; ni is its negation. Both return 1 or 0.
  • The right-hand side is a list — a braced literal or a variable holding one.
  • Matching is on whole elements, never substrings.
  • The comparison is textual, like eq. 1 in {1.0 2} is false.
  • So do not use it for numeric sets unless the text is written consistently.
  • It is ideal for modes, statuses and option names, which are characters rather than quantities.
  • Prefer it to lsearch in a condition — lsearch returns an index and needs != -1.
  • When several cases need different actions, use switch.

One chapter remains, and it explains why the comparison operators from earlier in this module should never be pointed at a measured number.

Where this fits

Part of the Tcl curriculum.