Android/Coroutine

[Kotlin Coroutines][init] 2. 시퀀스 빌더

네모메모 2024. 2. 18. 14:08
반응형

 

 

 

 

 

다른 언어에서는 코루틴을 어떻게 사용할까?? 🤔

파이썬이나 자바스크립트를 보면 아래 2가지 같은 제한된 형태의 코루틴이 있다고 한다.

  • 비동기 함수 (async/await와 같은 호출 방식)
  • 제너레이터(값을 순차적으로 반환)

 

 

 

코틀린에서는 제너레이터 대신 시퀀스를 생성할 수 있는 시퀀스 빌더라는 것을 대신 제공한다.
(Flow 빌더도 있으나 내용이 많아 추후 포스팅 예정)

 

 


 

시퀀스 빌더

- 시퀀스는 List, Set 같은 컬렉션과 비슷한 개념이지만 필요할 때마다 값을 하나씩 계산하는 지연 처리를 한다.

 

- [시퀀스의 특징]

  • 요구되는 연산을 최소한으로 수행 
  • 무한정이 될 수 있음 
  • 메모리 사용이 효율적
    (이펙티브 코틀린에  Item49 '하나 이상의 처리 단계를 가진 경우 시퀀스를 사용라라'고 있으니 참고)

 

- [시퀀스의 사용법]

  • sequence 블록을 이용해 정의
  • [sequence함수의 선언부] (코틀린 DSL로 구성됨)
    block: suspend SequenceScope.() -> Unit
더보기

block: suspend SequenceScope.() -> Unit

- 인자는  수신객체지정 람다함수 

- 람다 내부에서 수신객체인 this는 SequenceScope<T>를 가리킴

- 수신객체는 yield 함수를 가지고 있음

  • sequence 블록 안에서 yield()라는 함수를 통해 값을 생성할 수 있다.
  • distinct()함수 : 시퀀스(Sequence)에 있는 요소들 중에서 중복된 요소를 제거한 새로운 시퀀스를 반환하는 함수.
    중복된 요소는 첫 번째로 발견된 요소만을 유지하고 나머지 중복 요소들은 제거됩니다.
    기본적으로 요소의 동등성 비교를 위해 요소의 equals() 메서드를 사용합니다. 따라서 사용자 정의 클래스의 요소를 사용할 경우 equals() 메서드를 적절히 오버라이드하여 동등성을 정의해주어야 합니다.

 

- [시퀀스의 동작방식]

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("Next Number : $num")
    }
}

// Generating First
// Next Number : 1
// Generating Second
// Next Number : 2
// Generating Third
// Next Number : 3
// Done!

ㄴ> 반드시 알아야 하는 건 각 숫자가 미리 생성되는 대신 필요할 때마다 값이 생성된다는 것이다.
ㄴ> 반복문과 다른 결정적 차이는, 이전에 다른 숫자를 찾기 위해 멈췄던 지점에서 재실행된다는 점이다.
중단 체제가 없으면 함수가 중간에 멈췄다가 나중에 중단된 지점에서 다시 실행되는 건 불가능하다. 중단이 가능해서 메인 함수와 시퀀스 제너레이터가 번갈아가며 실행된다.

 

 

 

- [시퀀스의 사용1] 피보나치 수열 같은 수학적 시퀀스를 만드는 것이다.

import java.math.BigInteger

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() {
    println(fibonacci.take(10).toList())
}

// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

 

 

- [시퀀스의 사용2] 난수, 임의의 문자열을 만들 때

 

import kotlin.random.Random

fun randomNumbers(
    seed: Long = System.currentTimeMillis()
): Sequence<Int> = sequence {
    val random = Random(seed)
    while (true) {
        yield(random.nextInt())
    }
}

fun randomUniqueStrings(
    length: Int,
    seed: Long = System.currentTimeMillis()
): Sequence<String> = sequence {
    val random = Random(seed)
    val charPool = ('a'..'z') + ('A'..'Z') + ('0'..'9')
    while (true) {
        val randomString = (1..length)
            .map { i -> random.nextInt(charPool.size) }
            .map { charPool::get }
            .joinToString("")
        yield(randomString)
    }
}.distinct()

 

 

 
 

- [시퀀스 주의사항]  

시퀀스 빌더는 yield가 아닌 중단 함수를 쓰면 안 됩니다!!
중단이 필요하다면 데이터를 갖고 오기 위해 Flow를 사용하세요

ㄴ> SequenceScope에 RestrictsSuspension어노테이션이 있기 때문,  @RestrictsSuspension 어노테이션은 리시버가 SequenceScope가 아니면 중단함수를 호출하는 것을 허용하지 않습니다.

 

 

 

 

 

 

출처


- [Book] 코틀린 코루틴 : 안드로이드 및 백엔드 개발자를 위한 비동기 프로그래밍 마르친 모스카와 저 / 신성열 역 | 인사이트(insight) | 2023년 11월 01일

 

반응형