Stackful/Stackless 코루틴

Jooyung Han (한주영)
3 min readDec 12, 2017

--

이전 글: Knock!Knock! 코루틴

호베르투 이에루잘림스시 교수는 Lua에서 제공하는 코루틴이 First-class이며, Stackful하기 때문에 완전한(Full) 코루틴이라고 했다. (Asymmetric 한 것은 표현력에서 문제되지 않으며 오히려 대칭 코루틴에 비해 더 낫기까지 하다고 항변했다.)

C#, Python, JavaScript, Kotlin 등이 제공하는 코루틴은 완전할까? 이들 언어는 (직간접적으로) 코루틴을 일급 객체로 제공하기는 하지만 그 코루틴들이 Stackful하지 않다. 이 글에서는 Stackful/Stackless 를 조금 더 설명하고, Stackless 한 코루틴에서 Stackful한 효과를 얻는 예제를 JavaScript 로 살펴본다.

코루틴이 Stackful하다는 의미는 코루틴에서 다른 함수를 호출하여 그 함수에서 코루틴을 suspend 할 수 있음을 의미한다. 바꿔 말해 코루틴이 자체적으로 스택프레임을 가질 수 있다는 얘기다.

Stackless 코루틴은 ‘함수’라고 하는 중요한 추상화를 무력화 시킨다. 예를 들어 JavaScript 배열을 순회하는데는 for 문을 사용할 수도 있지만 Array.forEach 를 이용할 수도 있다.

let arr = [1,2,3];
for (const n of arr) {
// use n
}
arr.forEach((n) => {
// use n
});

forEach는 Generator 코루틴에서 무력화된다.

function* g() {
let arr = [1,2,3];
arr.forEach((n) => {
// can't use yield here
});
}

이러한 문제는 Async 코루틴에서도 똑같이 발생한다. JavaScript가 아니라 C#, Python, Kotlin 등의 다른 Stackless 코루틴을 지원하는 언어들도 똑같이 가지는 문제다. 코루틴 컨텍스트는 코루틴 중첩 호출을 통해서만 전달할 수 있기 때문에 기존의 ‘함수' 라이브러리들이 코루틴 용도로 재 정의되어야 해결된다.

C#은 아쉽게도 yield 중첩 문법이 없다. (하지만 LINQ가 있어서… ) Python이나 JavaScript는 yield from이나 yield*로 중첩 코루틴을 호출할 수 있다. Kotlin은 흥미롭게도 다른 키워드를 사용하지 않고 suspend 함수에서 다른 suspend 함수를 바로 호출할 수 있도록 했기 때문에 코루틴 중첩이 일반 함수 중첩처럼 매우 자연스럽게 보인다.

이전 글 Knock!Knock! 코루틴에서 아래와 같은 이벤트 핸들러 코루틴을 사용했다.

이 코드에서 input 객체를 아래처럼 만들 수 있다면 좋을 것이다.

const input = preset.map(() => getCode(yield))

하지만 JavaScript에서 이 코드는 올바르지 않다. yield 키워드는 반드시 function*()로 시작하는 제너레이터 함수에서만 사용할 수 있기 때문이다. Stackless란 얘기다.

위 코드는 두 개의 함수 호출이 중첩되어 yield가 사용되었다. Array.map과 맵 함수이다. 이 두 단계의 함수를 모두 Generator로 만든다면 Stackful 코루틴처럼 중첩 코루틴에서 yield할 수 있다.

코루틴 버전의 map() 함수와 매핑 함수의 도움으로 one-liner 도 가능하다. 물론 이게 더 낫다는 얘기는 아니다.

(yield* map(preset, function*(){return getCode(yield)}).join("") === preset ? success() : failure()

다음 글: Knock!Knock! 코루틴 #2

--

--

Jooyung Han (한주영)

가끔 함수형 프로그래밍 관련 글을 쓰거나 번역합니다. “개미 수열을 푸는 10가지 방법"이란 책을 썼습니다. https://leanpub.com/programming-look-and-say