ShareText
해찬
구독자 1명
제너레이터란
요즘은 알고리즘 문제를 푼 후에 ChatGPT에게서 피드백을 받는데, 종종 리스트 대신 제너레이터를 쓰라는 이야기를 듣는다. 이름은 많이 들어봤지만 무엇인지는 잘 몰라서 이 기회에 알아보려고 한다. 제너레이터는 지연 평가를 위한 장치다. 지연 평가란 함수를 실행하는 시점을 늦춘다는 이야기다. 함수가 실행되는 과정은 다음과 같다. 개발자가 소스 코드를 작성하고, 컴파일 타임에 해당 내용이 바이트 코드로 변환된다. 이후 개발자가 함수를 호출하면, 함수 프레임이 스택 영역에 쌓이고, 함수가 실행을 마친 후에는 함수 프레임이 스택 영역에서 제거된다. 즉, 함수 블록 내의 모든 코드는 함수를 호출했을 때 한 번에 실행된다. 하지만 어떤 경우에는 함수의 실행 시점을 제어해야 한다. 파일로부터 데이터를 읽어와서 화면에 표시해야 하는 경우, 얼마만큼의 용량을 읽어야 할까? 파일을 전부 다 읽으려고 하니까 메모리가 걱정된다. 그렇다고 일정량만 읽으려고 하니까 화면이 크면 데이터가 부족할 수 있어 걱정이다. 이런 때가 제너레이터를 쓸 때이다. 제너레이터는 원하는 때에 원하는 만큼만 실행할 수 있는 이터레이터이다. 그렇기 때문에 함수의 실행 시점을 제어할 수 있게 해준다. 사용 방식은 언어마다 약간씩 다르지만 대체로 비슷하다. 기존 함수는 키워드를 통해 값을 반환하고 함수를 종료했다면, 제너레이터 함수는 키워드를 통해 값을 반환하고 제너레이터 함수가 일시 종료된다. 일시 종료되었다는 말은 해당 제너레이터 함수를 다시 호출하면 이전 종료 시점부터 이어서 실행된다는 뜻이다. TypeScript와 Python에서도 사용 방법은 비슷하다. TypeScript에서는 대신 키워드를 쓰고 기존 반환 타입 를 로 바꾸면 된다. Python에서는 일반 함수에서 yield를 사용하면 자동으로 제너레이터가 되고, 리스트 컴프리헨션에서 대괄호(, ) 대신 소괄호(, )를 쓰면 된다. Kotlin의 경우 sequence 빌더가 타입 객체를 반환한다. 을 호출하여 next()를 직접 호출해도 되고, , 등의 함수를 사용해도 된다. TypeScript의 경우 타입 객체의 를 호출한다. Python의 경우 제너레이터 함수가 제너레이터 객체를 반환하고, 또는 와 같이 사용한다. 제너레이터 함수가 return을 호출한 이후 next()를 호출하는 경우 Kotlin과 Python에서는 예외가 발생한다(각각 , ). TypeScript에서는 를 반환한다. 세 언어 모두 for문을 사용하면 제너레이터 함수가 종료될 때까지 안전하게 순회한다. 사실 제너레이터로 할 수 있는 작업은 거의 대부분 리스트로도 할 수 있다. 그렇다면 제너레이터는 언제 써야할까? API 페이지네이션과 같이 결과 값을 저장할 필요 없이 한 방향으로 순회할 때, 또는 파일을 읽거나 무한 시퀀스를 만들 때처럼 메모리 효율이 중요할 때에도 제너레이터를 사용하면 좋다. 반면 데이터의 크기가 작은 경우, 데이터를 여러 번 순회하거나 인덱싱이 필요한 경우에는 리스트를 사용하는 것이 좋다. 제너레이터 함수는 이전 실행 상태를 기억한다. 그게 어떻게 가능한 걸까? 기본적으로 Kotlin, TypeScript, Python 모두 호출시에 함수 실행 정보를 저장한다. 다만 실행 정보를 저장하는 방식은 약간씩 다르다. Kotlin에서는 컴파일 타임에 제너레이터 함수를 상태 머신으로 변환한다. 즉, 제너레이터 함수를 상태에 따라 분기되는 일반 함수로 분해한다. 이후 런타임에 제너레이터 함수를 호출할 때마다 상태를 업데이트한 후에 제너레이터 함수를 실행한다. (이 역할은 Continuation 객체가 수행한다) TypeScript, Python에서는 런타임에 제너레이터 함수의 실행 정보를 제너레이터 객체에 저장한다. 제너레이터 함수를 재호출하면 제너레이터 객체에 저장된 정보를 스택 영역에 복원해서 이전 종료 지점부터 함수가 재개된다.
요즘은 알고리즘 문제를 푼 후에 ChatGPT에게서 피드백을 받는데, 종종 리스트 대신 제너레이터를 쓰라는 이야기를 듣는다. 이름은 많이 들어봤지만 무엇인지는 잘 몰라서 이 기회에 알아보려고 한다. 제너레이터는 지연 평가를 위한 장치다. 지연 평가란 함수를 실행하는 시점을 늦춘다는 이야기다. 함수가 실행되는 과정은 다음과 같다. 개발자가 소스 코드를 작성하고, 컴파일 타임에 해당 내용이 바이트 코드로 변환된다. 이후 개발자가 함수를 호출하면, 함수 프레임이 스택 영역에 쌓이고, 함수가 실행을 마친 후에는 함수 프레임이 스택 영역에서 제거된다. 즉, 함수 블록 내의 모든 코드는 함수를 호출했을 때 한 번에 실행된다. 하지만 어떤 경우에는 함수의 실행 시점을 제어해야 한다. 파일로부터 데이터를 읽어와서 화면에 표시해야 하는 경우, 얼마만큼의 용량을 읽어야 할까? 파일을 전부 다 읽으려고 하니까 메모리가 걱정된다. 그렇다고 일정량만 읽으려고 하니까 화면이 크면 데이터가 부족할 수 있어 걱정이다. 이런 때가 제너레이터를 쓸 때이다. 제너레이터는 원하는 때에 원하는 만큼만 실행할 수 있는 이터레이터이다. 그렇기 때문에 함수의 실행 시점을 제어할 수 있게 해준다. 사용 방식은 언어마다 약간씩 다르지만 대체로 비슷하다. 기존 함수는 키워드를 통해 값을 반환하고 함수를 종료했다면, 제너레이터 함수는 키워드를 통해 값을 반환하고 제너레이터 함수가 일시 종료된다. 일시 종료되었다는 말은 해당 제너레이터 함수를 다시 호출하면 이전 종료 시점부터 이어서 실행된다는 뜻이다. TypeScript와 Python에서도 사용 방법은 비슷하다. TypeScript에서는 대신 키워드를 쓰고 기존 반환 타입 를 로 바꾸면 된다. Python에서는 일반 함수에서 yield를 사용하면 자동으로 제너레이터가 되고, 리스트 컴프리헨션에서 대괄호(, ) 대신 소괄호(, )를 쓰면 된다. Kotlin의 경우 sequence 빌더가 타입 객체를 반환한다. 을 호출하여 next()를 직접 호출해도 되고, , 등의 함수를 사용해도 된다. TypeScript의 경우 타입 객체의 를 호출한다. Python의 경우 제너레이터 함수가 제너레이터 객체를 반환하고, 또는 와 같이 사용한다. 제너레이터 함수가 return을 호출한 이후 next()를 호출하는 경우 Kotlin과 Python에서는 예외가 발생한다(각각 , ). TypeScript에서는 를 반환한다. 세 언어 모두 for문을 사용하면 제너레이터 함수가 종료될 때까지 안전하게 순회한다. 사실 제너레이터로 할 수 있는 작업은 거의 대부분 리스트로도 할 수 있다. 그렇다면 제너레이터는 언제 써야할까? API 페이지네이션과 같이 결과 값을 저장할 필요 없이 한 방향으로 순회할 때, 또는 파일을 읽거나 무한 시퀀스를 만들 때처럼 메모리 효율이 중요할 때에도 제너레이터를 사용하면 좋다. 반면 데이터의 크기가 작은 경우, 데이터를 여러 번 순회하거나 인덱싱이 필요한 경우에는 리스트를 사용하는 것이 좋다. 제너레이터 함수는 이전 실행 상태를 기억한다. 그게 어떻게 가능한 걸까? 기본적으로 Kotlin, TypeScript, Python 모두 호출시에 함수 실행 정보를 저장한다. 다만 실행 정보를 저장하는 방식은 약간씩 다르다. Kotlin에서는 컴파일 타임에 제너레이터 함수를 상태 머신으로 변환한다. 즉, 제너레이터 함수를 상태에 따라 분기되는 일반 함수로 분해한다. 이후 런타임에 제너레이터 함수를 호출할 때마다 상태를 업데이트한 후에 제너레이터 함수를 실행한다. (이 역할은 Continuation 객체가 수행한다) TypeScript, Python에서는 런타임에 제너레이터 함수의 실행 정보를 제너레이터 객체에 저장한다. 제너레이터 함수를 재호출하면 제너레이터 객체에 저장된 정보를 스택 영역에 복원해서 이전 종료 지점부터 함수가 재개된다.
Kotlin
Python
Javascript
해찬
2025년 8월 24일
조회 3
실패에 관하여
얼마 전 API 실패 처리를 누락하는 바람에 서비스의 일부 유저에게 망가진 UI를 보여주고 말았다. (무한 스켈레톤!) 친절한 상사님께서는 실수는 병가지상사라는 재밌는 표현으로 위로해주셨지만, 실패 처리에 관한 교훈을 뼈저리게 느꼈다.(왜 플레이스토어 심사는 3일씩 걸리는가 ㅠㅠ) 그래서 오늘은 여러 종류의 실패와 처리 방법에 대한 글을 적어보려고 한다. 프런트엔드 개발자의 흔한 고민은 초기값 설정이다. 비동기 작업(보통은 API 호출)을 통해 값을 얻어와야 하니 초기값을 무엇으로 두어야 하는지, 비동기 작업 실패는 어떻게 처리해야 할지 항상 애매하다. 1년차 개발자인 현재는 초기값은 null, 실패는 Exception 반환으로 처리하고 있다. null은 사람마다 받아들이는 의미가 다르겠지만 일반적으로 , 을 나타낸다고 생각한다. 즉, 값이 설정되기 전에는 값이 없다는 의미로 null을 사용하는 것이다. 한편 값을 받아오지 못한 경우에는 Exception을 반환하도록 하는 편인데, 비정상적인 상황이라는 의미와 그 이유(.를 활용) 를 표현할 수 있고, Kotlin의 을 활용하여 성공/실패를 가독성 좋게 표현할 수 있으며, Kotlin에서는 표현이 Nothing 타입이기 때문에 메서드의 반환형에 얽매일 필요가 없어진다. 실패했음을 의미하는 플래그(또는 합의된 값)을 사용하거나 억지로 null을 반환할 필요가 없으니 얼마나 편리한가? 모바일 개발자인 나는 플레이스토어와 앱 스토어같은 플랫폼 사업자들의 옥죄어 오는 손길을 느낀다. Android의 경우 버전에 따라 권한, 백그라운드 동작, 유저 정보 관리 등 하루가 다르게 정책이 촘촘해지고 있는데, 이에 따라 분기점도 늘고 있다. 권한은 그 중에서도 가장 민감하다. 이라는 주관적인 기준에 따라 허용 여부가 결정되기도 하고, 자칫 잘못하면 제재를 받을 수 있다. 그렇기에 유저에게 은밀하면서도 정직하게 권한 허용을 유도하기 위해 신경을 쓰는데, 권한 거부는 자칫 관심이 소홀해질 수 있는 시나리오다. (ex. 설마 거부하겠어? 이거 거부할거면 우리 앱 왜 써? 유저가 권한을 거부하더라도 핵심 권한이 아니라면 우회로를 생각해보자. 예를 들어 스타벅스 앱에서 주문할 매장 선택하기 버튼을 누르면 위치 권한을 요구하는데, 거부하면 위치 정보 접근이 허용되지 않았다며 매장명을 가나다 순으로 안내한다. 구글과 친해지는 것도 하나의 방법이다. 요즘 만보기 앱이 늘면서 다양한 앱이 걸음수를 세어 주는데, 구현 방식은 다양하다. 하드웨어 센서를 이용할 수도 있고(핸드폰 끄면 초기화!), GoogleFit API를 사용할 수도 있다(구글 로그인 필수). 핸드폰이 언제 꺼질까 걱정하며 5분마다 걸음수를 서버로 보낼 것인가? 아니면 유저가 구글 아이디가 있고, 구글에 우호적이기만을 바라며 앱에 필요도 없는 구글 로그인을 강요할 것인가? 이에 대해 구글이 제시하는 좋은 방법이 있다. Local Recording API를 사용하면 구글 로그인 없이도 걸음수를 얻어올 수 있다. 여전히 신체 활동 권한은 필요하고, 저장 기간도 최대 10일이지만 이만하면 만보기로는 충분하다. 중국의 격언이라고 한다. 어쩌면 개발자(엔지니어)는 문제를 가장 많이 만나는 사람들이 아닐까 싶다. 옆에 있는 사람들을, 나 자신을 다독여가며 실패를 잘 넘어가보자.
얼마 전 API 실패 처리를 누락하는 바람에 서비스의 일부 유저에게 망가진 UI를 보여주고 말았다. (무한 스켈레톤!) 친절한 상사님께서는 실수는 병가지상사라는 재밌는 표현으로 위로해주셨지만, 실패 처리에 관한 교훈을 뼈저리게 느꼈다.(왜 플레이스토어 심사는 3일씩 걸리는가 ㅠㅠ) 그래서 오늘은 여러 종류의 실패와 처리 방법에 대한 글을 적어보려고 한다. 프런트엔드 개발자의 흔한 고민은 초기값 설정이다. 비동기 작업(보통은 API 호출)을 통해 값을 얻어와야 하니 초기값을 무엇으로 두어야 하는지, 비동기 작업 실패는 어떻게 처리해야 할지 항상 애매하다. 1년차 개발자인 현재는 초기값은 null, 실패는 Exception 반환으로 처리하고 있다. null은 사람마다 받아들이는 의미가 다르겠지만 일반적으로 , 을 나타낸다고 생각한다. 즉, 값이 설정되기 전에는 값이 없다는 의미로 null을 사용하는 것이다. 한편 값을 받아오지 못한 경우에는 Exception을 반환하도록 하는 편인데, 비정상적인 상황이라는 의미와 그 이유(.를 활용) 를 표현할 수 있고, Kotlin의 을 활용하여 성공/실패를 가독성 좋게 표현할 수 있으며, Kotlin에서는 표현이 Nothing 타입이기 때문에 메서드의 반환형에 얽매일 필요가 없어진다. 실패했음을 의미하는 플래그(또는 합의된 값)을 사용하거나 억지로 null을 반환할 필요가 없으니 얼마나 편리한가? 모바일 개발자인 나는 플레이스토어와 앱 스토어같은 플랫폼 사업자들의 옥죄어 오는 손길을 느낀다. Android의 경우 버전에 따라 권한, 백그라운드 동작, 유저 정보 관리 등 하루가 다르게 정책이 촘촘해지고 있는데, 이에 따라 분기점도 늘고 있다. 권한은 그 중에서도 가장 민감하다. 이라는 주관적인 기준에 따라 허용 여부가 결정되기도 하고, 자칫 잘못하면 제재를 받을 수 있다. 그렇기에 유저에게 은밀하면서도 정직하게 권한 허용을 유도하기 위해 신경을 쓰는데, 권한 거부는 자칫 관심이 소홀해질 수 있는 시나리오다. (ex. 설마 거부하겠어? 이거 거부할거면 우리 앱 왜 써? 유저가 권한을 거부하더라도 핵심 권한이 아니라면 우회로를 생각해보자. 예를 들어 스타벅스 앱에서 주문할 매장 선택하기 버튼을 누르면 위치 권한을 요구하는데, 거부하면 위치 정보 접근이 허용되지 않았다며 매장명을 가나다 순으로 안내한다. 구글과 친해지는 것도 하나의 방법이다. 요즘 만보기 앱이 늘면서 다양한 앱이 걸음수를 세어 주는데, 구현 방식은 다양하다. 하드웨어 센서를 이용할 수도 있고(핸드폰 끄면 초기화!), GoogleFit API를 사용할 수도 있다(구글 로그인 필수). 핸드폰이 언제 꺼질까 걱정하며 5분마다 걸음수를 서버로 보낼 것인가? 아니면 유저가 구글 아이디가 있고, 구글에 우호적이기만을 바라며 앱에 필요도 없는 구글 로그인을 강요할 것인가? 이에 대해 구글이 제시하는 좋은 방법이 있다. Local Recording API를 사용하면 구글 로그인 없이도 걸음수를 얻어올 수 있다. 여전히 신체 활동 권한은 필요하고, 저장 기간도 최대 10일이지만 이만하면 만보기로는 충분하다. 중국의 격언이라고 한다. 어쩌면 개발자(엔지니어)는 문제를 가장 많이 만나는 사람들이 아닐까 싶다. 옆에 있는 사람들을, 나 자신을 다독여가며 실패를 잘 넘어가보자.
해찬
2025년 1월 3일
조회 4