When is an isSet check on a collection genuinely needed?
← Comparison Patterns · Ref: Q640
Collections (List, Dict) are always set after construction, even when empty. Checking a newly constructed collection with ? is always redundant (E08088). A List returned from a function is set too: every route to a List goes through a constructor, so a function cannot hand back an unset List. The compiler does not flag a guard on a function's result (it does not look inside the function), but that guard always passes. An isSet check is genuinely needed only when the value can really be missing, and the way to say 'there may be no list at all' is to wrap it, for example in an Optional.
WHEN ISSET IS REDUNDANT (E08088)
After constructing a collection directly:
items <- List() of String if items? <- this is ALWAYS true (redundant)
The compiler knows the constructor always returns a set value.
NO RESULTS: TEST WITH EMPTY
A search that finds nothing returns an empty List, which is SET. Test it with 'empty':
noResults <- searchByKeyword("unknown") if noResults empty stdout.println("No results")
A guard such as 'if noResults <- searchByKeyword("unknown")' would ENTER its body here, because the empty List is set.
WHEN ISSET IS NEEDED
When there may be no collection at all, return an Optional of the collection and guard on that:
if scores <- lookupScores(studentId) stdout.println(`Found ${length scores.get()} scores`)
An Optional with nothing in it is unset, so the guard skips the body.
EMPTY VS UNSET
An empty list (0 items) is SET. It exists, it is valid, it just has no elements. An unset value has no meaningful value yet. These are different states. Use 'empty' to check for no elements; use '?' (or a guard) on something that can really be unset, such as an Optional.
CORRECT PATTERN FOR EMPTY CHECK
Use the empty operator to check for no elements:
if items empty stdout.println("No items")
Do NOT use '?' to check if a list has elements.
See Q559 for redundant isSet detection. See Q558 for tautological conditions. See Q641 for return value capture.
Example
defines module qa.comparison.collectionisset defines function <?- Simulates a search. It always returns a SET List: empty when nothing matches. -?> searchByKeyword() -> keyword as String <- results as List of String: List() of String if keyword == "ek9" results += "Introduction to EK9" results += "EK9 Best Practices" <?- Simulates a lookup. Like every List, the result is set; it is empty for an unknown student. -?> findScores() -> studentId as Integer <- scores as List of Integer: List() of Integer if studentId == 1 scores += 85 scores += 92 scores += 78 <?- A lookup where there may be NO list at all: the Optional is unset for an unknown student. -?> lookupScores() -> studentId as Integer <- rtn as Optional of List of Integer: Optional() of List of Integer found <- findScores(studentId) if found not empty rtn: Optional(found) <?- Pure function that processes a list. -?> countAboveThreshold() as pure -> items as List of Integer threshold as Integer <- counted as Integer: 0 for item in items if item > threshold counted: counted + 1 defines program CollectionIsSetDemo() stdout <- Stdout() //A constructed collection is set, with or without items: test it with 'empty', never '?' fresh <- List() of String for title in searchByKeyword("rust") fresh += title if fresh empty stdout.println("Nothing collected, but the List is still set") //A List returned from a function is set too, so this guard always enters if results <- searchByKeyword("ek9") stdout.println(`Found ${length results} results`) for item in results stdout.println(` ${item}`) //No results: the List is SET but empty, so test it with 'empty', not '?' noResults <- searchByKeyword("unknown") if noResults empty stdout.println("No results for 'unknown'") //Genuinely needed: an Optional that may hold no List at all if scores <- lookupScores(1) above80 <- countAboveThreshold(scores.get(), 80) stdout.println(`Scores above 80: ${above80}`) missing <- lookupScores(2) stdout.println(`Scores held for student 2: ${missing?}`) //Empty check on a returned List for studentId in [1, 2] knownScores <- findScores(studentId) if knownScores empty stdout.println(`Empty scores for student ${studentId}`) else stdout.println(`Has ${length knownScores} scores`)
Common mistakes
E08088 — A collection made by its constructor is always set, so checking it with ? is always true and EK9 rejects the check. Test for no elements with 'empty'. See ek9 -h E08088 for details.
Incorrect:
fresh <- List() of String if fresh?
Correct:
fresh <- List() of String for title in searchByKeyword("rust") fresh += title if fresh empty
Other ways to ask this
- Is checking a list with ? redundant after construction?
- What is E08088 redundant isSet check on collection?
- When should I check if a collection is set?
Coming from another language?
Java: no tracking of collection initialization state. Kotlin: nullable collection vs empty collection distinguished by type. Rust: Option<Vec<T>> vs empty Vec<T>. Go: nil slice vs empty slice. Python: None vs empty list. EK9: tri-state (absent, unset, set), collections are always set after construction (a List returned from a function included), E08088 for redundant isSet check, 'empty' for no elements, Optional of List when there may be no list at all.
Keywords: collection, redundant, check, comparison, dict, constructed, E08088, pattern, compare, E08089, empty, isSet, list