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