코틀린 코루틴 (2장 정리)

윤성현·2024년 12월 12일

코틀린 코루틴

목록 보기
2/11
post-thumbnail

2장. 시퀀스 빌더

시퀀스 빌더와 시퀀스가 작동하기 위해 왜 중단이 필요한지 알아보자

📝 서론

파이썬이나 자바스크립트 등에서는 제한된 형태의 코루틴을 사용

코틀린에서는 제너레이터 대신 시퀀스 빌더를 제공하며, 시퀀스는 필요할 때마다 값을 하나씩 계산하는 지연 처리가 가능

✨ 시퀀스의 특징

  • 최소한의 연산 수행: 필요한 값만 계산 🎯
  • 메모리 효율적: 필요한 만큼만 값을 계산하여 사용 💾
  • 무한 시퀀스 가능: 예를 들어, 자연수나 피보나치 수열을 무한히 생성할 수 있음 ♾️
// 자연수의 무한 시퀀스
val naturalNumbers = sequence {
    var n = 1
    while (true) {
        yield(n++)
    }
}

// 피보나치 수열의 무한 시퀀스
val fibonacci = sequence {
    var terms = Pair(0, 1)
    while (true) {
        yield(terms.first)
        terms = Pair(terms.second, terms.first + terms.second)
    }
}

이러한 무한 시퀀스는 실제로 모든 값을 한 번에 계산하지 않고, take()나 first() 등의 연산자를 통해 필요한 만큼만 값을 계산합니다. 이것이 시퀀스의 지연 처리 특성입니다.

🏗️ 시퀀스 빌더

위와 같은 특징으로 값을 순차적으로 계산하여 필요할 때 반환하는 빌더를 정의하는 것이 좋음

시퀀스는 sequence 함수를 이용하며 람다식 내부에서 yield 함수를 호출하여 다음 값을 생성

val seq = sequence {
	yield(1)
	yield(2)
	yield(3)
}

fun main() {
	for (num in seq) {
		print(num)
	} // 123
}
  • sequence 함수는 DSL 코드
  • 인자는 수신 객체 지정 람다 함수 (suspend SequenceScope.() → Unit)
  • 람다 내부에서 수신 객체인 this 는 SequenceScope

🔄 시퀀스의 실행 흐름

val seq = sequence {
	println("Generating first")
	yield(1)
	println("Generating second")
	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 second
// The next number is 2
// Generating third
// The next number is 3
// Done
  • main 함수에서는 num을 받아올 때 seq.next() 를 호출하며, yield 로 값을 반환할때까지 실행 🏃‍♂️
  • 중단이 가능하기 때문에 main 함수와 시퀀스 제너레이터가 번갈아가면서 실행됨 🔀

🔍 이터레이터 사용

val seq = sequence {
	println("Generating first")
	yield(1)
	println("Generating second")
	yield(2)
	println("Generating third")
	yield(3)
	println("Done")
}

fun main() {
	val iterator = seq.iterator()
	println("Starting")
	val first = iterator.next()
	pritnln("First: $first")
	val second = iterator.next()
	println("Second: $second")
	// ...
}

// Starting
// Generating first
// The next number is 1
// Generating second
// The next number is 2
  • 이터레이터는 다음 값을 얻기 위해 사용 📤
  • 어떤 지점이든 상관없이 이터레이터를 호출하면 빌더 함수의 이전 지점으로 돌아갈 수 있음 🔙
  • 스레드로 이런 작업을 하려면 유지하고 관리하는 데 더 큰 비용이 발생 💡

실제 사용 예

피보나치 수열과 같은 수학적 시퀀스 🔢

val fibonacci: Sequence<BigInteger> = sequence {
	var first = 0.toBigInteger()
	var second = 1.toBigInteger()
	while (true) {
		yield(first)
		val temp = first
		first += second
		second = temp
	}
}

fun main() {
	print(fibonacci.take(10).toList())
}
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

⚠️ 주의사항

  • 시퀀스 빌더는 반환(yield)이 아닌 중단 함수를 사용하면 안 됨 🚫
  • 중단이 필요하다면 플로우를 사용하는 것이 적합 🌊

0개의 댓글