How do I use Optional in stream pipelines?

← Getting Started · Ref: Q85

Optional integrates with EK9's stream pipelines through the flatten operator. A List of Optional values can be flattened to extract only present values, discarding empty ones.

FLATTEN

Extract present values from a list of Optionals:

  optionals <- [Optional(5), Optional() of Integer, Optional(14)]
  cat optionals | flatten > stdout

The empty Optional contributes nothing. Only 5 and 14 pass through.

COLLECT WITH FLATTEN

Aggregate present values:

  sum <- cat optionals | flatten | collect as Integer

Sum equals 19. This is EK9's equivalent of Java's Stream.flatMap(Optional::stream) but more intuitive.

OPTIONAL AS ITERATOR

Optional supports the iterator protocol, yielding 0 or 1 elements:

  present <- Optional("Hello")
  while iter <- present.iterator() then iter.hasNext()
    stdout.println($iter.next())

An empty Optional's iterator yields nothing. This makes Optional compatible with any code that expects an iterable.

See Q47 for Optional basics (creation, guards, getOrDefault). See Q84 for Optional operations (comparison, copy, merge). See Q59 for function pipelines. See Q123 for flattening nested collections and lists of lists.

Example

defines module qa.optional.streams

  defines program
    OptionalStreams()
      stdout <- Stdout()

      // === FLATTEN ===

      // Extract present values from list of Optionals
      optionals <- [Optional(5), Optional() of Integer, Optional(14)]
      cat optionals | flatten > stdout

      // === COLLECT WITH FLATTEN ===

      // Sum only the present values
      sum <- cat optionals | flatten | collect as Integer
      require sum == 19
      stdout.println(`Sum of present values: ${sum}`)

      // Inline form
      mixed <- [Optional(10), Optional() of Integer, Optional(20)]
      sum2 <- cat mixed | flatten | collect as Integer
      stdout.println(`Sum2: ${sum2}`)

      // === OPTIONAL AS ITERATOR ===

      // Present Optional yields one element
      present <- Optional("Hello")
      while iter <- present.iterator() then iter.hasNext()
        stdout.println(`Iterator: ${iter.next()}`)

      // Empty Optional yields nothing
      emptyOpt <- Optional() of String
      while iter <- emptyOpt.iterator() then iter.hasNext()
        stdout.println("Should not print")

      stdout.println("Done")

Common mistakes

E07840 — The 'cat' operation requires an iterable source (List, Dict, Range, Optional, etc.). Using a non-iterable Integer value triggers E07840 — cannot iterate over that type. See ek9 -h E07840 for details.

Incorrect:

cat sum | flatten | collect as Integer

Correct:

cat mixed | flatten | collect as Integer

E50060 — EK9 Optional does not have a Java-style '.stream()' method. Use '.iterator()' to get an iterator that yields 0 or 1 elements. See ek9 -h E50060 for details.

Incorrect:

present.stream()

Correct:

present.iterator()
Other ways to ask this
  • How does flatten work with Optional in EK9?
  • How do I extract present values from a list of Optionals?
  • Can Optional be used as an iterator?

Coming from another language?

Java: Stream.flatMap(Optional::stream) to extract present values, verbose compared to EK9's flatten. Rust: .filter_map(|x| x) or .flatten() on Iterator<Option<T>>. Kotlin: .filterNotNull() on lists of nullable types. Python: list comprehension [x for x in items if x is not None]. EK9: cat optionals | flatten — single intuitive operator extracts present values from any list of Optionals.

Keywords: flatten, extract, safe, collect, iterator, aggregate, first, optional, guard, start, absent, stream, pipeline, migrate, intro, present, beginner