파이썬이나 자바스크립트 같은 언어에서는 제한된 형태의 코루틴을 사용하고 있다.
코틀린에서 async가 사용되는 건 앞에서 이미 살펴봤으며, 6장 ‘코루틴 빌더’에서 더 자세하게 다룬다. 코틀린에서는 제너레이터 대신 시퀸스를 생성할 때 사용하는 시퀸스 빌더를 제공하고 있다.
<aside> 📢 코틀린은 시퀸스보다 더 좋은 방법인 플로우 빌더도 제공한다.
</aside>
코틀린의 시퀸스는 List, Set과 같은 컬렉션이랑 비슷한 개념이지만, 필요할 때마다 값을 하나씩 계산하는 지연(lazy) 처리를 한다. 시퀸스의 특징은 다음과 같다.
시퀸스 특징과 관련하여 Effective Kotlin 54번 내용을 참고하자. 54. Prefer Sequences for big collections with more than one processing step
이러한 특징 때문에 값을 순차적으로 계산하여 필요할 때 반환하는 빌더를 정의하는 것이 좋다. 시퀸스는 sequence라는 함수를 이용해 정의한다. 시퀸스의 람다 표현식 내부에서는 yield 함수를 호출하여 시퀸스의 다음 값을 생성한다.
val seq = sequence {
yield(1)
yield(2)
yield(3)
}
fun main() {
for (num in seq) {
print(num) // 123
}
}
위 코드에서 사용한 sequence 함수는 짧은 DSL(Domain-Specific Language, 도메인 전용 언어) 코드다. 인자는 수신 객체 지정 람다 함수다(suspend SequenceScope<T>.() → Unit). 람다 내부에서 수신 객체인 this는 SequenceScope<T>를 가리킨다. 이 객체는 yield 함수를 가지고 있다. this가 암시적으로 사용되므로 yield(1)을 호출하면 this.yield(1)을 호출하는 것과 동일하다.
여기서 반드시 알아야 하는 것은 각 숫자가 미리 생성되는 대신, 필요할 때마다 생성된다는 점이다. 시퀸스 빌더 내부 그리고 시퀸스를 사용하는 곳에서 메시지를 출력하면 이러한 작동 방식을 쉽게 확인할 수 있다.
val seq = sequence {
println("Generating first")
yield(1)
println("Generating seconds")
yield(2)
println("Generating third")
yield(3)
println("Done")
}
fun main() {
for (num in seq) {
println("The next number is $num")
}
}
/**
* Generating first
* The next number is 1
* Generating seconds
* The next number is 2
* Generating third
* The next number is 3
* Done
*/
시퀸스의 작동 방식에 대해 알아보자. 우선 첫 번째 수를 요청하면 빌더 내부로 진입한다. “Generating fist”를 출력한 뒤, 숫자 1을 반환한다. 이후 반복문에서 반환된 값을 받은 뒤, “Next number is 1”을 출력한다. 여기서 반복문과 다른 결정적인 차이가 보인다. 이전에 다른 숫자를 찾기 위해 멈췄던 지점에서 다시 실행이 된다. 중단 체제가 없으면 함수가 중간에 멈췄다가, 나중에 중단된 지점에서 다시 실행되는 건 불가능하다. 중단이 가능하기 때문에 main 함수와 시퀸스 제너레이터가 번갈아가면서 실행된다.