Process a list of optional sensor readings and select the minimum using stream join with <?.

← Streams and Pipelines · Ref: Q1233

Define a pure function using <? and pass it to 'join with' in a stream pipeline. The join operation reduces the stream to a single value.

  coolerOf() as pure
    -> left as SensorReading, right as SensorReading
    <- rtn as SensorReading: left <? right
  coolest <- cat readings | join with coolerOf | collect as SensorReading

The <? operator handles unset values gracefully within the stream. The custom <=> on temperature determines ordering.

See Q1139 for join basics. See Q1230 for custom <=> on records.

Example

defines module qa.streams.coalesce.pipeline

  defines record

    SensorReading
      stationId as String: String()
      temperature as Float: 0.0

      SensorReading()
        ->
          stationId as String
          temperature as Float
        this.stationId: stationId
        this.temperature: temperature

      operator <=> as pure
        -> other as SensorReading
        <- rtn as Integer: temperature <=> other.temperature

      default operator

  defines function

    coolerOf() as pure
      ->
        left as SensorReading
        right as SensorReading
      <- rtn as SensorReading: left <? right

    warmerOf() as pure
      ->
        left as SensorReading
        right as SensorReading
      <- rtn as SensorReading: left >? right

  defines program

    CoalesceInPipelineDemo()
      stdout <- Stdout()

      // === BUILD A LIST OF SENSOR READINGS ===

      readings <- List() of SensorReading
      readings += SensorReading("North", 22.5)
      readings += SensorReading("South", 18.3)
      readings += SensorReading("East", 25.1)
      readings += SensorReading("West", 15.8)

      // === FIND COOLEST USING STREAM JOIN WITH <? ===

      coolestList <- cat readings | join with coolerOf | collect as List of SensorReading
      stdout.println(`Coolest: ${coolestList}`)

      // === FIND WARMEST USING STREAM JOIN WITH >? ===

      warmestList <- cat readings | join with warmerOf | collect as List of SensorReading
      stdout.println(`Warmest: ${warmestList}`)

      // === SORTED OUTPUT ===

      stdout.println("All readings sorted by temperature:")
      cat readings | sort > stdout

Common mistakes

E50060 — EK9 List has no .stream() method; reduce with a stream pipeline 'cat list | join with fn'. See ek9 -h E50060 for details.

Incorrect:

coolestList <- readings.stream().min()

Correct:

coolestList <- cat readings | join with coolerOf | collect as List of SensorReading
Other ways to ask this
  • How do I reduce a stream of custom records to find the minimum?
  • Given sensor readings from multiple stations, find the coolest using a pipeline.
  • In Java I'd use stream().min(Comparator.comparing(SensorReading::getTemperature)). What does EK9 use?
  • Migrating from Python where I use min(readings, key=lambda r: r.temperature) — how does EK9 do this?

Coming from another language?

Java: stream().min(Comparator.comparingDouble(SensorReading::getTemperature)). Python: min(readings, key=lambda r: r.temperature). Rust: readings.iter().min_by_key(|r| r.temperature). EK9: cat readings | join with coolerOf | collect as SensorReading.

Keywords: reduce, minimum, coalescing, join, stream, <?, sensor, pipeline, record