5강: 코이펙트: 계산이 환경에게 필요로 하는 것
한 줄 요약: 이펙트는 「프로그램이 세상에 무엇을 했는가」를 다루고, 코이펙트는 「세상이 프로그램에게 무엇에 의존할 것을 요구하는가」를 다룹니다. 코이펙트가 답하는 것은 「계산이 환경으로부터 무엇을 제공받아야 하는가」(접근해야 할 리소스, 갖춰야 할 능력, 의존해야 할 서비스)이며, 이펙트와는 방향이 반대인, 서로 쌍대인 개념입니다.
1. 쌍대로 여는 도입: 하나는 「내가 무엇을 바꿨는가」를 묻고, 다른 하나는 「내가 무엇을 필요로 하는가」를 묻습니다
먼저 두 가지 사고 실험을 합시다. 둘 다 여러분이 매일 겪는 일입니다.
배달 주문하기: 이것은 「이펙트」입니다. 여러분이 「주문하기」를 누륵면 세상은 정말로 바뀝니다. 가게가 조리를 시작하고, 라이더가 주문을 접수하고, 여러분의 지갑에서 돈이 빠져나가고, 길 위에 배달 상자가 하나 더 늘어납니다. 프로그램(이 주문 행위)이 세상에 영향을 미쳤고, 그 영향은 프로그램에서 세상으로 흐릅니다.
일하고 월급 받기: 이것은 「코이펙트」입니다. 여러분은 이제 막 입사한 프로그래머입니다. 자리, 컴퓨터, 네트워크, 데이터베이스 권한, 프로젝트 문서…… 어느 하나 여러분이 가져온 것이 없고, 전부 회사가 제공합니다. 이것들이 없으면 코드 한 줄도 쓸 수 없습니다. 월급은 말할 것도 없이 「세상이 여러분에게 주는 것」입니다. 여기서는 환경(회사)이 프로그램(여러분)에게 물건을 제공하는 것이며, 방향이 정확히 반대입니다.
이 두 예를 논문의 언어로 번역하면, 이번 강의의 핵심이 됩니다.
이펙트는 프로그램이 세상에 미치는 영향을 특징짓고, 코이펙트는 세상이 프로그램에 가하는 제약을 특징짓는다.
- 배달 주문 = 프로그램이 세상을 바꿈 → 이펙트
- 일하기 = 세상이 프로그램에게 리소스, 능력, 보수를 제공 → 코이펙트
그렇다면 「세상이 프로그램에 가하는 제약」은 구체적으로 어떤 모습일까요? 논문은 하나의 목록을 제시합니다. 그것은 계산이 자신의 환경에 요구하는 것을 기술하는데, 예를 들면 다음과 같습니다.
| 필요한 것 | 프로그램에서의 모습 |
|---|---|
| 접근해야 할 리소스 | 데이터베이스 연결, 파일, 메모리, 네트워크 |
| 갖춰야 할 능력 | 어떤 권한, 하나의 키, 어떤 API를 호출할 자격 |
| 의존해야 할 서비스 | 외부 시스템, 다른 플러그인이 제공하는 기능 |
타입 시스템 안에서 코이펙트는 이런 모습입니다. 일반적인 판단은 Γ ⊢ t : T(컨텍스트 Γ 아래에서 t의 타입은 T)입니다. 코이펙트 시스템은 판단을 Γcoeffect ⊢ t : T로 씁니다. 컨텍스트 Γ 자체가 「코이펙트 대수」의 원소로 주석이 달리고, 이 주석이 바로 「이 계산이 환경에 요구하는 것」입니다.
效应问「我改了什么」(对世界的影响);余效应问「我需要什么」(世界对我的约束)
위의 그림과 대조해 볼 때, 이 「쌍대」만 기억하면 충분합니다. 이펙트는 계산에서 세상을 가리키고(내가 무엇을 바꿨는가), 코이펙트는 세상에서 계산을 가리킵니다(내가 무엇을 필요로 하는가). 한쪽이 풍요롭게 하는 것은 타입이고, 다른 한쪽이 풍요롭게 하는 것은 컨텍스트입니다.
| 이펙트(effect) | 코이펙트(coeffect) | |
|---|---|---|
| 묻는 질문 | 내가 무엇을 바꿨는가? | 내가 무엇을 필요로 하는가? |
| 방향 | 계산 → 세상 | 세상 → 계산 |
| 특징짓는 것 | 프로그램이 세상에 미치는 영향 | 세상이 프로그램에 가하는 제약 |
| 풍요롭게 하는 것 | 타입 | 컨텍스트 |
| 일상의 예 | 배달 주문: 주문 후 세상이 바뀐다 | 일하기: 회사가 자리, 권한, 월급을 준다 |
2. 코모나드적 코이펙트: 프로그램은 「환경에 잠겨 있다」
지난 강의에서 우리는 모나드, 즉 「값을 상자에 넣는」 디자인 패턴을 만났습니다. 이번 강의의 쌍대는 **코모나드(comonad)**입니다. 모나드는 부수효과를 상자에 넣지만, 코모나드는 프로그램이 원래부터 컨텍스트(환경)에 잠겨 있다고 가정하고, 매 단계에서 환경으로부터 무언가를 「꺼낼」 수 있습니다.
논문에서 가장 직관적인 예는 환경 코모나드로, 다음과 같이 씁니다.
D(X) = E × X
이 공식을 두려워할 필요는 없습니다. 그것이 말하는 것은 사실 이것입니다. 모든 계산 X는 환경 E를 몸에 지니고 함께 달린다는 것입니다. 스마트폰 앱이 항상 「현재 시각」과 「현재 위치」라는 두 개의 환경 값을 갖고 다니며, 어느 단계에서든 그것들을 읽을 수 있는 것과 같습니다.
코모나드에는 두 가지 기본 동작이 있고, 직관적으로는 다음과 같습니다.
- ε(꺼내기): 컨텍스트에서 현재 값을 추출합니다——「컨텍스트에서 현재 값을 꺼낸다」. 프로그램이 환경을 읽고 싶다고요? 손을 뻗으면 바로 있습니다.
- δ(복제): 컨텍스트를 한 부 복사해서 중첩된 안쪽 계산에 걸려줍니다——「중첩된 접근을 위해 컨텍스트를 복제한다」. 안쪽 함수가 환경을 읽고 싶다고요? 환경이 그것을 따라갑니다.
논문은 또한 스트림 코모나드를 언급하는데, D(X) = ℕ → X로 씁니다. ℕ은 자연수(시점이라고 이해핼 수 있습니다)로, 어떤 시각을 주면 하나의 값을 반환합니다. 그것이 특징짓는 것은 시계열 데이터에 대한 의존입니다. 예를 들어 시계를 따라 움직여야 하는 컴포넌트나, 데이터 스트림을 따라 움직이는 컴포넌트 같은 것입니다.
🐟 비유를 들어봅시다. 모나드 안의 프로그램은 밀봉된 상자 같아서 세상을 밖에 닫아 두지만, 코모나드 안의 프로그램은 물고기 같아서 시시각각 물(환경)에 잠겨 있고, 숨을 쉴 때마다(계산의 매 단계마다) 물에서 산소를 얻습니다. 물고기가 어디로 헤엄쳐 가든 물은 따라갑니다. 이것이 「컨텍스트 의존 계산」입니다.
그래서 「코모나드적 코이펙트」의 한 줄 버전은 이렇습니다. 프로그램의 결과는 그것이 놓인 환경에 의존하며, 환경은 필요한 것을 스스로 걸려준다.
3. 그레이디드 코이펙트: 「얼마나 필요한가」에 무게 라벨을 붙이다
「프로그램이 환경에 의존한다」고만 말하는 것으로는 부족합니다. 얼마나 의존하는가도 중요합니다. 어떤 변수가 0번 쓰이는 것과 100번 쓰이는 것은 리소스 소모 면에서 하늘과 땅 차이입니다.
그레이디드 코이펙트(graded coeffects)의 방법은 모든 「요구」에 무게 라벨을 붙이는 것입니다. 논문에서 이 라벨 체계는 하나의 **반환(semiring)**이며, 다음과 같이 씁니다.
S = (S, ≤, +, ×, 0, 1)
기호에 겁먹지 마세요. 그냥 「덧셈, 뺄셈, 곱셈, 나눗셈이 가능한 계량 시스템」으로 이해하면 됩니다. 라벨에는 네 가지 의미가 있고, 아주 일상적입니다.
| 라벨 | 의미 | 비유 |
|---|---|---|
| 0 | 미사용: 이 변수는 처음부터 끝까지 한 번도 쓰이지 않았다 | 전원을 켠 적이 없는 가전제품 |
| 1 | 선형 사용: 정확히 한 번 쓰였다 | 일회용 식기, 쓰고 나면 버린다 |
| n | 유계 사용: 최대 n번까지 쓰인다 | 횟수가 정해진 체험권 |
| ∞ | 무제한 사용: 몇 번이든 쓰고 싶은 대로 쓸 수 있다 | 무제한 데이터 요금제 |
두 가지 연산은 두 가지 「결합 방식」에 대응합니다.
- ×(곱셈): 순차 결합——A를 먼저 끝내고 B를 하면, 무게는 곱해집니다. 예를 들어 「파일을 한 번 읽고, 다시 한 번 읽는다」는 1 × 1입니다.
- +(덧셈): 병렬 결합——A와 B가 동시에 진행되면, 무게는 더해집니다.
💡 비유를 들어봅시다. 집에 수도·전기 계량기를 달아 둔 것과 같습니다. 「이 코드는 이 파일을 최대 n번까지 읽는다」라고 선언하면, 시스템은 실행하기 전에 초과가 없는지 검사할 수 있습니다. 두 작업이 동시에 돌아가면, 시스템은 그 사용량을 합산해 총 결산을 합니다.
이 「계량 시스템」이 있으면, 논문에 따륵면 하나의 통일된 대수적 프레임워크 안에서 이전에는 할 수 없던 몇 가지 일을 할 수 있습니다.
- 정밀한 리소스 추적——이 변수가 도대체 몇 번 쓰였는지, 조회하면 바로 압니다.
- 민감도 분석——누구의 입력이 누구의 출력에 영향을 미치고, 그 영향이 얼마나 큰지 한눈에 압니다.
- 정보 흐름 제어——민감한 데이터가 안전하지 않은 곳으로 흘러갈 수 있는지, 전 과정을 검사할 수 있습니다.
한마디로, 그레이디드 코이펙트 = 코이펙트 + 사용량 장부이며, 「무엇이 필요한가」를 「얼마나 필요한가」로 업그레이드하는 것입니다.
4. 정적에서 동적으로: 컴포저빌리티와의 관계
이제 이 논문이 진정으로 관심을 갖는 문제로 돌아갑시다. 동적 컴포저빌리티입니다(기억하시나요? 컴포넌트는 장착했다가 떼어낼 수 있고, 떼어낸 뒤 흔적이 남지 않는다). 논문의 2.3절은 이펙트와 코이펙트를 컴포저빌리티의 두 차원에 각각 대응시킵니다.
- 시간적 컴포저빌리티 → 상태를 가진 이펙트. 시간적 컴포저빌리티는 「컴포넌트가 공유 환경에 가한 변경이, 컴포넌트 언인스톨 시 가역적이어야 한다」를 요구합니다. 환경을 진정으로 지속적으로 바꾸는 것은 상태를 가진 이펙트(파일 쓰기, 데이터베이스 변경)입니다. 이런 변경을 가역적으로 만들려면, 그 변환이 반드시 역변환이 존재해야 합니다(취소할 수 있어야 합니다).
- 공간적 컴포저빌리티 → 코이펙트. 공간적 컴포저빌리티는 「컴포넌트 간의 의존을 선언하고, 리액티브한 방식으로 관리한다」를 요구합니다. 이러한 의존은 바로 코이펙트가 포착하는 내용입니다. 의존을 관리한다는 것은, 환경이 실제로 제공하는 것에 근거하여 각 의존을 하나씩 해석해 내는 것입니다.
| 컴포저빌리티 차원 | 묻는 질문 | 대응하는 개념 |
|---|---|---|
| 시간적 컴포저빌리티 | 언인스톨 후에 환경이 복원될 수 있는가? | 상태를 가진 이펙트(변경에는 역변환이 있어야 함) |
| 공간적 컴포저빌리티 | 컴포넌트는 무엇에 의존하는가? 누가 제공하는가? | 코이펙트(의존 선언 + 리액티브한 해석) |
그렇다면 문제입니다. 고전적인 이펙트 시스템, 코이펙트 시스템이 동적 조합을 곧바로 뒷받침할 수 있을까요?
논문의 답은 단호합니다. 없습니다. 그것들은 정적인 도구이기 때문입니다.
- 이펙트는 어휘적으로 고정된 스코프 안에서 추적되고, 컴파일 시점의 핸들러에 의해 제거됩니다.
- 코이펙트 주석은 실행 전에 이미 확정된 컨텍스트에 근거해 검증됩니다.
이 두 가지는 「세상은 컴파일 시점에 확정된다」라는 가정 아래에서는 성립하지만, 동적 조합의 세상은 그렇지 않습니다.
| 정적 시스템의 가정 | 동적 조합의 현실 |
|---|---|
| 스코프는 어휘적으로 박혀 있다 | 플러그인은 배포된 후에야 로드되므로, 고정 스코프로는 그것들을 가둘 수 없다 |
| 컨텍스트는 컴파일 시점에 확정할 수 있다 | 의존은 런타임 설정에서 생겨날 수 있어, 컴파일 시점에는 도저히 예견할 수 없다 |
⚠️ 그래서 논문은 여기서 관점을 전환합니다. 더 많은 주석을 추가해 정적 타입 시스템을 확장하는 대신, 이펙트와 코이펙트의 개념적 구조를 구체화(reify)하여 런타임이 그것들을 직접 조작할 수 있게 합니다. 그리하여 이 시스템들이 정적인 경우에 제공하던 보장을, 런타임에 동적으로 세워 올리는 것입니다.
이것이 「컴파일 시점의 증명」에서 「런타임의 메커니즘」으로의 결정적인 도약입니다. 어떻게 구체화하고, 어떻게 런타임이 조작하게 할 것인지는 논문 제3절의 내용이니, 다음 강의에서 만납시다.
핵심 정리
이번 강의는 정보가 적지 않지만, 이 다섯 문장만 기억하면 충분합니다.
- 쌍대: 이펙트는 「내가 무엇을 바꿨는가」를 묻고(프로그램이 세상에 미치는 영향), 코이펙트는 「내가 무엇을 필요로 하는가」를 묻습니다(세상이 프로그램에 가하는 제약). 배달 주문은 이펙트이고, 일하고 월급 받기는 코이펙트입니다.
- 코이펙트가 주석을 다는 것은 컨텍스트:
Γcoeffect ⊢ t : T에서 컨텍스트는 코이펙트 대수의 원소로 주석이 달리며, 계산이 환경에 요구하는 것——접근해야 할 리소스, 갖춰야 할 능력, 의존해야 할 서비스——을 기술합니다. - 코모나드 = 환경에 잠긴 계산: 환경 코모나드
D(X) = E × X는 모든 계산이 환경을 지니고 달린다는 것을 나타냅니다. ε는 환경에서 현재 값을 꺼내고, δ는 환경을 복제해 중첩된 계산에 걸려줍니다. - 그레이디드 코이펙트 = 사용량 장부: 반환
S = (S, ≤, +, ×, 0, 1)의 라벨이 사용을 정량화합니다——0은 미사용, 1은 선형, n은 유계, ∞는 무제한. ×는 순차 결합, +는 병렬 결합으로, 리소스 추적, 민감도 분석, 정보 흐름 제어를 뒷받침합니다. - 정적에서 동적으로: 시간적 컴포저빌리티는 상태를 가진 이펙트(가역적 변경)에 대응하고, 공간적 컴포저빌리티는 코이펙트(의존)에 대응합니다. 정적 시스템은 런타임에 로드되는 플러그인을 가둘 수 없으므로, 논문은 이펙트/코이펙트를 런타임이 직접 조작할 수 있는 메커니즘으로 끌어올립니다.
🚀 다음 강의에서는 논문 제3절의 첫 번째 주인공인 롤백 가능한 이펙트에 들어갑니다. 「장착할 수 있고, 떼어낼 수 있다」. 이펙트가 어떻게 런타임에 진정으로 취소될 수 있을까요? 그것이 바로 시간적 컴포저빌리티에 대한 답입니다.
자가 테스트 · 코이펙트
답을 모두 선택한 후 「답안 제출」을 클릭하면 정답 여부와 해설을 확인할 수 있습니다.
