List, Set과 같은 컬렉션은 핫이며, Sequence와 자바의 Stream은 콜드다. Channel은 핫이지만 Flow와 (Observable, Single과 같은) RxJava 스트림은 콜드다.(*일반적인 경우에는 맞지만 예외도 있다. buffer와 channelFlow와 같은 몇몇 함수와 빌더는 플로우도 핫 스트림이 될 수 있도록 지원한다. 또한 SharedFlow와 StateFlow도 핫이다.)

Hot vs Cold

Hot data stream은 데이터를 소비하는 것과 무관하게 원소를 생성하지만, cold data stream은 요청이 있을 때만 작업을 수행하며 아무것도 저장하지 않는다.

Hot인 List와 Cold인 Sequence를 사용할 때 그 차이가 드러난다. 핫 데이터 스트림의 빌더와 연산은 즉각 실행된다. 콜드 데이터 스트림에서는 원소가 필요할 때까지 실행되지 않는다.

fun main() {
    val l = buildList {
        repeat(3) {
            add("User$it")
            println("L: Added User")
        }
    }
    val l2 = l.map {
        println("L: Processing")
        "Processed $it"
    }

    val s = sequence {
        repeat(3) {
            yield("User$it")
            println("S: Added User")
        }
    }
    val s2 = s.map {
        println("S: Processing")
        "Processed $it"
    }
}

그 결과 콜드 데이터스트림은

Sequence는 원소를 지연 처리하기 때문에 더 적은 연산을 수행한다. (map이나 filter 같은) 중간 연산은 이전에 만든 시퀸스에 새로운 연산을 첨가할 뿐이다. 최종 연산(terminal operation)이 모든 작업을 실행한다.(* 다른 유형을 반환하는 시퀸스에 대한 연산. 일반적으로 find나 toList를 사용)

시퀸스의 경우 find는 map의 결과물에서 첫 번째 원소를 달라고 한다. sequenceOf에서 반환된 시퀸스(1을 반환한다)에 질의를 하고, 맵을 수행한 뒤(1이 된다), filter에 넘긴다. filter는 주어진 원소가 요구 사항에 부합하는지 확인한다. 원소가 요구사항을 만족하지 못한다면, filter는 적절한 원소를 찾을 때까지 계속해서 질의한다.

시퀸스의 처리 방식은 리스트의 처리 방식(모든 중간 과정을 계산하고 모든 데이터 처리가 완료된 컬렉션을 반환)과 아주 다르다. 따라서 리스트의 경우 원소의 처리 순서가 달라지며, 컬렉션 처리 과정에서 좀 더 많은 메모리를 필요로 하고, 더 많은 연산을 수행하게 된다.

fun m(i: Int): Int {
    print("m$i ")
    return i * i
}

fun f(i: Int): Boolean {
    print("f$i ")
    return i >= 10
}

fun main() {
    listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        .map { m(it) }
        .find { f(it) }
        .let { print(it) }

		// m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 f1 f4 f9 f16 16
    println()

    sequenceOf(1,2,3,4,5,6,7,8,9,10)
        .map { m(it) }
        .find { f(it) }
        .let { print(it) }
    
    // m1 f1 m2 f4 m3 f9 m4 f16 16					   
}

리스트는 원소의 컬렉션이지만, 시퀸스는 원소를 어떻게 계산할지 정의한 것에 불과하다.

핫 데이터 스트림은

fun main() {
    val l = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        .map { m(it) }
    // m1 m2 m3 m4 m5 m6 m7 m8 m9 m10

    println(l) // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    println(l.find { it > 10 }) // 16
    println(l.find { it > 10 }) // 16
    println(l.find { it > 10 }) // 16

    val s =sequenceOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        .map { m(it) }
    // m1 m2 m3 m4 m5 m6 m7 m8 m9 m10
    
    println(s.toList()) // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    println(s.find { it > 10 }) // m1 m2 m3 m4 16
    println(s.find { it > 10 }) // m1 m2 m3 m4 16
    println(s.find { it > 10 }) // m1 m2 m3 m4 16
}