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). However, when a collection comes from a function return, the check may be genuinely needed.
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.
WHEN ISSET IS NEEDED
When the collection comes from a function that might return unset:
if results <- searchItems(query) process(results)
The function might return an unset List to signal 'no results found'.
EMPTY VS UNSET
An empty list (0 items) is SET. It exists, it is valid, it just has no elements. An unset list does not yet have a meaningful value. These are different states. Use 'empty' to check for no elements, use '?' to check for existence.
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 that might return an unset list. -?> 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 that might return unset. -?> findScores() -> studentId as Integer <- scores as List of Integer: List() of Integer if studentId == 1 scores += 85 scores += 92 scores += 78 <?- 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() //Guard on function return: genuinely needed if results <- searchByKeyword("ek9") stdout.println(`Found ${length results} results`) for item in results stdout.println(` ${item}`) //Guard on function that might return unset if scores <- findScores(1) above80 <- countAboveThreshold(scores, 80) stdout.println(`Scores above 80: ${above80}`) //No results case if noResults <- searchByKeyword("unknown") stdout.println("Should not print") else stdout.println("No results for 'unknown'") //Empty check: appropriate when list IS set but might have no items knownScores <- findScores(1) if knownScores? if knownScores empty stdout.println("Empty scores") else stdout.println(`Has ${length knownScores} scores`)
Common mistakes
E50001 — Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details.
Incorrect:
countAboveThreshold(scores, 80)
Correct:
above80 <- countAboveThreshold(scores, 80)
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, E08088 for redundant isSet check.
Keywords: pattern, E08088, constructed, dict, comparison, check, redundant, collection, list, isSet, empty, E08089, compare