스폰서LobeHubLobeHub자세히 알아보기
dshfind

10강: 컨텍스트 패러다임: 컨텍스트 타입의 통일

한 줄 요약: 컨텍스트 패러다임(Context Paradigm)은 「내가 어디에 있는가(현재 상태), 내가 무엇을 바꿨는가(역함수), 내가 무엇을 필요로 하는가(의존성 테이블)」이라는 세 가지를 모두 하나의 재귀 가능한 ctx 엔티티에 담습니다. 이를 통해 컴포넌트의 「플러그인·플러그아웃」은 단순한 비유에서 실제로 구현할 수 있는 구조로 바뀌고, 실행 취소와 재연결의 정확성은 더 이상 개발자의 자율이 아니라 구조 자체에 의해 보장됩니다.

1단계: 하나의 ctx에 세 가지를 담는다——「내가 어디에 있는가, 무엇을 바꿨는가, 무엇을 필요로 하는가」

먼저 앞 레슨들의 두 가지 주요 흐름을 떠올려 봅시다.

  • 이펙트(effect): 컴포넌트가 작업할 때 남기는 부수 효과(파일 변경, 프로세스 시작……). 실행 취소가 가능해야 합니다.
  • 코이펙트(coeffect): 컴포넌트가 작업할 때 필요로 하는 것들(어떤 서비스, 어떤 설정……). 자동으로 연결되어야 합니다.

이전에는 이 둘을 각각 다른 두 가지 메커니즘으로 처리했습니다. 이번 절의 논문은 과감한 통일을 시도합니다. 이펙트, 코이펙트, 그리고 「현재 상태」를 모두 같은 엔티티에 넣는 것입니다. 바로 재귀적인 컨텍스트 타입입니다.

정의 24(컨텍스트 타입): Γ∞ ≔ μΓ. Γ × (Γ → Γ) × Σ

이 수식을 읽지 못해도 괜찮습니다. 뜯어 보면 한 문장입니다. 하나의 ctx 안에 세 가지가 동시에 담겨 있다는 것입니다.

구성 요소담고 있는 것쉬운 말로한 줄 비유
Γ현재 컨텍스트 상태(재귀적)내가 지금 어디에 있는가, 세상이 지금 어떻게 보이는가현재 위치
Γ → Γ누적 역함수내가 그동안 무엇을 바꿨는가, 어떻게 한 단계씩 되돌아갈 것인가실행 취소 로그
Σ의존성 테이블내가 지금 필요로 하는 것들장보기 목록

세 가지 구성 요소를 합치면, 컴포넌트가 환경과 상호작용하기 전에 매번 답해야 하는 세 가지 질문이 됩니다. 나는 어디에 있는가? 내가 무엇을 바꿨는가? 나에게 아직 무엇이 부족한가? 모든 상호작용은 이 하나의 ctx 엔티티만을 거칩니다.

统一上下文类型 Γ∞(ctx)Γ · 当前状态现在环境是什么样(递归嵌套)Γ→Γ · 逆函数怎么把改动撤销(累积恢复变换)Σ · 依赖表我需要什么(键 → 有类型的值)

一个 ctx 同时记住「我在哪、我改了什么、我需要什么」——效应与余效应合体

왜 「재귀」라고 부를까요? 첫 번째 구성 요소 Γ 안에 담긴 것도 여전히 같은 Γ이기 때문입니다. 러시아 인형(마트료시카)처럼 모든 층이 같은 종류의 것이므로 원하는 만큼 깊이 중첩할 수 있습니다. 이처럼 「자기가 자기를 품는」 구조에는 전용 명칭이 있습니다. 바로 자기 유사성입니다. 논문 앞부분에서는 이펙트를 점점 높아지는 「𝜕 타워」로 추상화했는데, 여기서는 재귀를 사용해 타워 전체를 하나의 타입으로 평탄화했습니다.

한 단계 더 깊이 들여다보면, 이 설계는 두 가지 추가 이점도 가져옵니다.

  • 이펙트가 ctx 위의 자기 준동형(엔도모피즘)이 된다: 한 번의 이펙트란 「ctx 하나를 가지고 들어가서, 새 ctx와 역함수 하나를 가지고 나오는 것」입니다. 들어가고 나오는 것이 같은 타입이므로 어떤 이펙트든 자유롭게 이어 붙일 수 있습니다.
  • Σ는 모든 공유 상태를 담을 수 있다: Σ가 기반으로 하는 타입에 제한이 없기 때문에, 컴포넌트 간에 공유하고 싶은 어떤 전역 상태든 Σ 안의 타입이 있는 의존성 항목으로 인코딩할 수 있습니다. 다시 말해 Σ가 다루는 범위는 「컴포넌트 간 의존성」뿐 아니라 모든 공유 가변 상태입니다.

2단계: 계층적 합성——플러그인·플러그아웃은 정말 「꽂기」와 「뽑기」뿐이다

지난 레슨(컴포넌트 생명주기)에서 「컴포넌트는 플러그처럼 꽂고 뽑을 수 있다」고 말했습니다. 이제 Γ∞가 생겼으니, 이 비유는 마침내 수사에서 구조로 바뀝니다.

ctx는 재귀적이므로, 자식 컴포넌트의 ctx는 자연스럽게 부모 컴포넌트의 ctx 안에 중첩됩니다. 부모 컨텍스트가 여러 자식 계층의 이펙트를 집계하여 트리 형태의 제어 구조를 형성합니다.

        ┌───────────────┐
        │   부모 컨텍스트  │  ← 모든 자식 컴포넌트의 이펙트를 집계·관리
        └───────┬───────┘
    ┌───────────┼───────────┐
    ▼           ▼           ▼
 [컴포넌트 A]  [컴포넌트 B]  [컴포넌트 C]   ← 각각 독립적으로 꽂고 뽑으며, 서로 영향을 주지 않음

「플러그인·플러그아웃」은 두 가지 연산으로 직접 구현됩니다.

연산하는 일쉬운 말로
컴포넌트 마운트그 이펙트를 실행한다(삽입)플러그를 꽂으면 효과가 적용된다
컴포넌트 언마운트그 이펙트를 복원한다(뽑기)플러그를 뽑으면 효과가 취소된다

이 설계에는 세 가지 핵심 보장이 있습니다.

  1. 뽑아도 다른 것에 영향을 주지 않는다: 컴포넌트를 언마운트하면 그 컴포넌트 자신의 이펙트만 복원되며, 실행 중인 다른 컴포넌트에는 전혀 영향을 주지 않습니다.
  2. 계층끼리 서로 간섭하지 않는다: 트리의 서로 다른 계층에 있는 컴포넌트는 각각 독립적으로 마운트·언마운트할 수 있으며, 전역적으로 통일된 순서가 필요하지 않습니다.
  3. 임의의 깊이로 중첩 가능: 부모 컨텍스트는 모든 자식 컴포넌트의 이펙트를 집계·관리하고, 자식 컴포넌트는 다시 자식 컴포넌트를 품을 수 있습니다. 몇 겹이든 원하는 대로 중첩할 수 있습니다.

🎁 비유하자면: 부모 컨텍스트는 「여러 콘센트가 달린 멀티탭」과 같습니다. 기기 하나를 뽑아도 다른 기기에는 그대로 전원이 공급되고, 멀티탭 자체를 또 다른 멀티탭에 꽂아 무한히 연장할 수도 있습니다.

3단계: 같은 이펙트, 두 가지 구현——인플레이스냐 파생이냐

논문은 여기서 매우 중요한 개념의 구분을 합니다. 지시(denotation)와 구현(implementation)의 분리입니다.

어떤 연산을 Γ∞ 위의 이펙트로 타입을 매기면, 고정되는 것은 그 지시입니다. 즉 「후속 상태 하나 + 대응하는 역함수 하나」입니다. 하지만 구현은 고정되지 않습니다. 그 역함수를 실제로 어떻게 실행할지는 구현 쪽에서 결정합니다.

정의 25(이펙트 함수의 두 가지 구현): 이펙트 함수 f는 두 가지 구현 방식을 허용합니다.

인플레이스 구현(in-place)파생 구현(derived)
컨텍스트원래 컨텍스트를 수정하고, 후속 상태는 입력의 별칭이 된다입력을 변경하지 않고, 재귀 구조 안에서 새 컨텍스트를 반환한다
역함수비자명한 역함수를 반환한다(변경 내용을 실제로 기록함)항등 함수를 반환한다(아무것도 바꾸지 않았으므로 취소할 것이 없음)
복원 방식역함수를 실행하여 방금 한 수정을 되돌린다파생된 새 컨텍스트를 폐기한다
비유계약서 원본에 직접 수정하고, 매 변경을 실행 취소 목록에 기록하며, 되돌릴 때는 목록을 역순으로 따라가며 고친다사본을 한 부 뜨고 사본에만 수정하며 원본은 건드리지 않는다. 필요 없어지면 사본을 파쇄기에 넣는다

「지시」와 「구현」의 구분에 주의하세요. 같은 이펙트라도 지시는 고정(후속 상태 + 역함수)이지만, 두 구현 중 하나를 자유롭게 선택할 수 있습니다. 어느 쪽을 고를지는 호스트 환경에 따라 달라집니다.

  • 순수 함수형 환경에서는 「인플레이스 수정」이라는 것 자체가 없으므로, 두 구현이 서로 일치합니다(둘 다 파생만 가능합니다).
  • 명령형 호스트에서는 개발자가 연산 단위로 자유롭게 선택할 수 있습니다. 빠르게 바꾸고 싶으면 인플레이스를, 원본 데이터를 건드리기 싫으면 파생을 사용합니다.

💡 논문은 4.1.2절에서 두 구현의 대표적인 작성법을 제공한다고 말합니다. 다음 레슨에서 그것들이 어떤 모습인지 보게 될 것입니다.

4단계: 왜 이것이 하나의 「패러다임」일까?——두 가지 낡은 사고방식과의 비교

논문의 야심은 「타입 하나를 제시하는 것」에 그치지 않고, 이 컨텍스트 타입 자체가 하나의 프로그래밍 패러다임을 이룬다고 주장하는 데 있습니다. 이를 이해하려면 먼저 두 가지 낡은 패러다임이 부수 효과를 어떻게 처리해 왔는지 살펴야 합니다. 둘은 같은 스펙트럼의 양극에 서 있습니다.

극좌: 명시적 상태 전달(함수형)

순수 함수형 언어는 참조 투명성을 유지하기 위해 부수 효과를 상태에 대한 명시적 변환으로 모델링합니다. 고전적인 예는 상태 모나드 S → (A, S)로, 환경을 모든 계산에 관통시킵니다.

  • 장점: 이펙트가 타입에 드러나므로 등식 추론이 가능하고 추적 가능성이 매우 뛰어나다.
  • 대가: 호출 체인의 모든 함수가 상태 매개변수를 받고 반환해야 합니다. 상태를 그대로 아래로 넘기기만 하더라도 마찬가지입니다. 이펙트 차원이 늘어나면(로깅, 설정, I/O) 모나드 스택이나 이펙트 핸들러의 보일러플레이트가 급격히 팽창합니다.

🎁 비유하자면: 회사가 「모든 문서는 모든 직원의 손을 거쳐 전원의 서명을 받아야 한다」고 규정한 것과 같습니다. 그저 전달만 하더라도 마찬가지입니다. 추적은 되지만, 진이 빠집니다.

극우: 암묵적 변경(명령형 / OOP)

주류 명령형 언어는 컴포넌트가 공유 상태를 직접 수정하고 의존성을 직접 가져올 수 있게 하며, 호출 지점에서는 아무것도 선언할 필요가 없습니다.

  • 장점: 작성하기 편하고 인체공학성이 매우 뛰어나다.
  • 대가: 추적 불가능. 논문은 두 가지 생생한 예를 듭니다.
    • 이펙트 측: React의 useEffect 훅——컴포넌트 내부의 파이버 위에 지속적인 부수 효과를 등록하지만, 이펙트 대상과 등록 메커니즘 모두 명시적 매개변수가 아니며, 그 정체성은 런타임 상태에 숨겨진 호출 순서상의 위치로 결정됩니다.
    • 코이펙트 측: Java의 서비스 로케이터(예: Spring의 getBean(...))——전역 레지스트리에서 의존성을 가져오고, 모든 호출 지점에서 null 체크와 타입 캐스팅을 해야 하며, 의존 관계는 코드베이스 전체에 암묵적으로 흩어져 있습니다.

더 나쁜 것은, f()가 시스템의 무엇을 바꾸고 무엇에 의존하는지 이해하려면 호출 관계를 따라 그 구현을 재귀적으로 읽어야 한다는 점입니다. 리팩터링도 그만큼 취약해집니다. 어떤 호출을 옮기거나 삭제하는 것만으로도 멀리 떨어진 불변 조건을 소리 없이 망가뜨릴 수 있습니다.

🎁 비유하자면: 공용 칠판으로, 누구나 쓸 수 있지만 아무도 이름을 남기지 않는 것과 같습니다. 편리하지만, 사고가 나면 누가 바꿨는지 찾을 수 없습니다.

중간: 컨텍스트 패러다임——두 가지 장점을 모두 취한다

컨텍스트 패러다임은 이 양극을 꿰매어 잇습니다. 이펙트와 코이펙트 모두 명시적인 컨텍스트 매개변수를 통해 매개됩니다. 따라서:

  • 모든 연산은 그것을 호출한 ctx에 귀속되고, 나아가 그 ctx를 소유한 컴포넌트에 귀속됩니다. 함수형처럼 추적 가능합니다.
  • 개발자는 호출 체인에 상태 매개변수를 잔뜩 손으로 적을 필요가 없습니다. 명령형처럼 간결합니다.

그리고 이것은 단순한 「절충」이 아니라, 정확성 보장까지 한 단계 업그레이드합니다.

상황기존 방식(자율에 의존)컨텍스트 패러다임(구조에 의존)
롤백 가능한 이펙트모든 복합 연산마다 개발자가 직접 실행 취소 로직을 작성해야 함원자 연산에 역함수만 제공하면, 복합 연산의 역함수는 합성에 의해 자동으로 얻어진다——마운트와 해체가 구조적으로 가역적이 된다
반응형 코이펙트의존성이 제대로 연결되었는지는 전적으로 주의력에 달림컴포넌트는 필요한 의존성만 선언하면 런타임이 자동으로 해석하고 재연결한다——프로바이더가 추가·제거·교체되어도 연결은 항상 올바르게 유지된다

두 방향을 합치면 이 절의 핵심 결론이 됩니다. 원래 개발자의 자율로 보장해야 했던 정확성이, 이 패러다임의 구조적 속성이 된 것입니다.

핵심 정리

정보량이 적지 않았지만, 이 다섯 문장만 기억하면 됩니다.

  1. 통일 컨텍스트: Γ∞ ≔ μΓ. Γ × (Γ → Γ) × Σ——하나의 ctx가 「현재 상태 Γ」「누적 역함수 Γ → Γ」「의존성 테이블 Σ」를 동시에 담는다. 즉 「내가 어디에 있는가, 무엇을 바꿨는가, 무엇을 필요로 하는가」.
  2. 재귀적 자기 유사성: Γ 안에 든 것도 Γ로, 러시아 인형처럼 원하는 만큼 깊이 중첩할 수 있다. 이펙트는 ctx 위의 자기 준동형이 되고(ctx가 들어가고, ctx와 역함수가 나옴), Σ는 모든 공유 가변 상태를 인코딩할 수 있다.
  3. 계층적 합성: 부모 컨텍스트는 자식 이펙트를 집계해 트리 구조를 형성한다. 마운트 = 이펙트 삽입, 언마운트 = 뽑기와 복원. 서로 영향을 주지 않고 임의 깊이의 중첩을 지원한다.
  4. 지시와 구현의 분리: 이펙트는 「지시」(후속 상태 + 역함수)를 고정하고 구현은 고정하지 않는다——인플레이스 구현(ctx 수정 + 비자명한 역) 또는 파생 구현(새 ctx 반환 + 항등 역, 복원 = 폐기).
  5. 패러다임으로서의 위치: 함수형의 명시적 상태 전달은 추적 가능하지만 보일러플레이트가 많고, 명령형의 암묵적 변경은 사용하기 편하지만 추적이 불가능하다. 컨텍스트 패러다임은 명시적 ctx로 두 가지를 모두 얻어, 정확성을 「개발자의 자율」에서 「구조적 보장」으로 바꾼다.

🚀 탁상공론은 여기까지입니다. 다음 레슨은 논문 4장 「구현과 사례 연구」로 들어갑니다. Cordis 코어 라이브러리에서 Γ∞가 어떻게 실제 코드가 되는지, 인플레이스 / 파생 두 구현이 실제로 어떻게 작성되는지 살펴야겠습니다.

셀프 체크 · 컨텍스트 패러다임

답을 모두 선택한 후 「답안 제출」을 클릭하면 정답 여부와 해설을 확인할 수 있습니다.

1. 통일 컨텍스트 타입 Γ∞ 안에 동시에 담긴 세 가지는 무엇입니까?
2. 계층적 합성에 대해 가장 정확한 설명은 무엇입니까?
3. 인플레이스 구현과 파생 구현의 차이에 대해 올바른 설명은 무엇입니까?
4. 「함수형의 명시적 상태 전달」과 「명령형의 암묵적 변경」에 비해 컨텍스트 패러다임의 장점은 무엇입니까?