우리 모두를 위한 대수적 효과
React의 Hooks와 Suspense 등을 설계한 핵심 개발자의 멘탈 모델로 쓰이는 '대수적 효과(Algebraic Effects)'를 개발자의 관점에서 쉽게 풀어 설명한 글입니다. 전통적인 예외 처리(try/catch)와 달리, 프로그램의 실행을 중단시키지 않고 중간에 상태를 복구해 원래 진행 중이던 코드로 되돌아갈 수 있다는 점이 가장 큰 특징입니다. 아직 상용 프로그래밍 언어에 널리 적용되지는 않았지만, 자바스크립트의 async/await가 등장하기 전의 상황과 같은 혁신적인 패러다임으로 평가받고 있습니다.
우리 모두를 위한 대수적 효과 (Algebraic Effects for the Rest of Us)
2019년 7월 21일 원하는 만큼 결제하세요
'대수적 효과(Algebraic Effects)'에 대해 들어보셨나요? 제가 처음 그것이 무엇이며 왜 관심을 가져야 하는지 알아내려고 했을 때의 시도들은 실패로 돌아갔습니다. 몇 가지 PDF 문서를 찾았지만 저를 더 헷갈리게 만들었을 뿐입니다. (학술용 PDF 문서에는 저를 졸리게 만드는 무언가가 있는 것 같습니다.) 하지만 저의 동료인 세바스찬(Sebastian)은 React 내부에서 우리가 하는 일들에 대한 멘탈 모델로 계속해서 이를 언급했습니다. (세바스찬은 React 팀에서 일하고 있으며 Hooks와 Suspense를 포함한 꽤 많은 아이디어를 고안해 낸 사람입니다.) 어느 시점부터 이것은 React 팀 내의 반복적인 농담거리가 되었고, 우리의 많은 대화가 다음과 같이 끝나곤 했습니다. 결국 대수적 효과는 멋진 개념이며 제가 그 PDF 문서들에서 생각했던 것만큼 무섭지 않다는 것이 밝혀졌습니다. React를 사용하는 데 있어 이에 대해 알 필요는 없습니다. 하지만 저처럼 호기심이 생긴다면 계속 읽어보세요. (면책 조항: 저는 프로그래밍 언어 연구원이 아니며, 제 설명에서 뭔가 잘못된 부분이 있을 수 있습니다. 저는 이 주제에 대한 권위자가 아니니 오류가 있다면 알려주세요!)
아직 프로덕션(상용) 단계는 아닙니다
대수적 효과는 연구 중인 프로그래밍 언어 기능입니다. 즉, if 문이나 함수, 심지어 async/await와 달리 아마 당장은 프로덕션 환경에서 이를 실제로 사용할 수 없다는 뜻입니다. 이 개념은 오로지 그 아이디어를 탐구하기 위해 특별히 만들어진 소수의 언어에서만 지원됩니다. OCaml에서 이를 프로덕션 환경에 도입하려는 진전이 있었는데... 아직도 진행 중입니다. 달리 말하자면, 아직 손댈 수 없는 영역(Can’t Touch This)입니다. 편집: 몇몇 사람들이 LISP 언어가 비슷한 것을 제공한다고 언급했으므로, LISP으로 코드를 작성한다면 프로덕션에서 사용할 수 있습니다.
그럼 왜 관심을 가져야 하나요?
goto 문을 사용하여 코드를 작성하고 있다고 상상해 보세요. 그리고 누군가가 if 문과 for 문을 보여줍니다. 혹은 콜백 지옥(Callback Hell)에 깊이 빠져 있는데 누군가가 async/await를 보여준다고 상상해 보세요. 꽤 멋지지 않나요? 만약 여러분이 새로운 프로그래밍 아이디어가 주류로 자리 잡기 몇 년 전에 미리 배우는 것을 좋아하는 타입의 사람이라면, 대수적 효과에 호기심을 가질 좋은 시기일 수 있습니다. 하지만 꼭 그래야 한다는 압박감은 느끼지 마세요. 이는 1999년에 async/await를 미리 생각해 보는 것과 조금 비슷합니다.
알겠습니다, 그래서 대수적 효과가 정확히 뭔가요?
이름은 조금 생소할 수 있지만, 아이디어 자체는 매우 단순합니다. try/catch 블록에 익숙하다면 대수적 효과도 매우 빠르게 이해할 수 있을 것입니다. 먼저 try/catch를 복습해 봅시다.
예를 들어, 예외를 throw(던지는) 하는 함수가 있다고 가정해 보겠습니다. 아마도 그 함수와 catch 블록 사이에는 여러 함수가 존재할 것입니다: (예시 코드 생략)
getName 내부에서 throw를 하지만, 이것은 makeFriends를 통과해 바로 가장 가까운 catch 블록까지 "버블링(거품처럼 올라감)"됩니다. 이것이 try/catch의 매우 중요한 속성입니다. 중간에 있는 코드들은 에러 처리에 신경 쓸 필요가 없습니다. C 언어와 같은 에러 코드 방식과 달리, try/catch를 사용하면 에러를 잃어버릴까 봐 두려워하며 모든 중간 계층을 통해 수동으로 에러를 전달할 필요가 없습니다. 에러는 자동으로 전파됩니다.
이것이 대수적 효과와 무슨 상관이 있나요?
위의 예시에서, 일단 에러가 발생하면 우리는 더 이상 코드를 계속 진행할 수 없습니다. catch 블록에 도달하는 순간, 원래 코드의 실행을 이어갈 방법은 없습니다. 끝난 것입니다. 너무 늦어버렸습니다. 우리가 할 수 있는 최선의 방법은 실패로부터 복구하고 어쩌면 우리가 하던 일을 어떻게든 재시도하는 것이지만, 마법처럼 원래 있던 곳으로 "되돌아가서" 다른 작업을 수행할 수는 없습니다.
하지만 대수적 효과를 사용하면 가능합니다. 이것은 user.name이 누락된 상황에서 복구할 수 있게 해주는, 가상의 자바스크립트 방언(재미를 위해 ES2025라고 부르겠습니다)으로 작성된 예시입니다: (예시 코드 생략)
(2025년에 웹에서 "ES2025"를 검색하다가 이 글을 발견한 모든 독자분들께 사과드립니다. 그때쯤 대수적 효과가 자바스크립트의 일부가 되어 있다면 기꺼이 이 글을 업데이트하겠습니다!)
throw 대신에 가상의 perform 키워드를 사용합니다. 마찬가지로 try/catch 대신에 가상의 try/handle을 사용합니다. 여기서 정확한 문법은 중요하지 않습니다. 단지 아이디어를 설명하기 위해 제가 만들어낸 것입니다.
그래서 무슨 일이 일어나고 있는 걸까요? 자세히 살펴봅시다. 에러를 throw(던지는) 대신, 효과(effect)를 수행(perform)합니다. 어떤 값이든 throw 할 수 있는 것처럼, 우리는 perform에 어떤 값이든 전달할 수 있습니다. 이 예시에서는 문자열을 전달하고 있지만...