본문 바로가기

SICP 2.4.3, 하스켈 타입클래스로 다시 짓기

반응형

Data-directed programming in Haskell (SICP 2.4.3)
Data-directed programming in Haskell (SICP 2.4.3)

 

⚡ 한 줄 요약: SICP 2.4.3의 '데이터 지향 프로그래밍(put/get 디스패치 표)'은 하스켈에선 타입클래스로 거의 그대로 옮겨지지만, 런타임 표가 아닌 컴파일타임·타입안전 디스패치라는 점이 핵심 차이다.

 

 

📑 목차

 

 

📌 무슨 일인가

해커뉴스에 SICP(계산기 프로그램의 구조와 해석) 2장 2.4.3절 "데이터 지향 프로그래밍(Data-directed programming)"을 하스켈로 다시 구현한 글이 올라와 토론이 붙었습니다.
원문은 Scheme으로 쓰인 SICP의 제네릭 연산 예제를 하스켈로 옮기면서, SICP의 핵심 장치인 '연산×타입 → 함수' 조회 테이블이 하스켈에선 어떤 언어 기능과 대응하는지를 짚습니다.
결론부터 말하면, 그 자리를 타입클래스(typeclass)가 메꿉니다.
다만 성격이 다릅니다.

 

 

🧩 데이터 지향 프로그래밍이 뭐였나

SICP는 "여러 표현을 가진 데이터에 대해 하나의 제네릭 연산을 만드는" 방법을 세 가지로 정리합니다.

  • 타입 검사 분기(dispatch on type): 연산마다 if 직교좌표면… else 극좌표면…을 넣는 방식. 표현이 하나 늘 때마다 모든 연산을 다 고쳐야 합니다.
  • 메시지 전달(message passing): 데이터 자체가 연산을 들고 다니는 방식.
  • 데이터 지향(data-directed): (연산, 타입태그) → 프로시저를 담는 표(table)를 만들고, put으로 등록·get으로 꺼내 실행. 새 표현을 추가할 땐 표에 열 하나만 더 붙이면 되고 기존 코드는 손대지 않습니다.

이 마지막 방식의 미덕이 바로 가산성(additivity) — "기존 코드 수정 없이 확장"입니다.

 

 

💡 개발자에게 주는 의미

여기서 눈치챈 분도 있을 겁니다.
이건 사실 그 유명한 Expression Problem(표현 문제)의 절반입니다.
필립 와들러가 정리한 이 문제는 "새 타입 추가새 연산 추가를 둘 다, 기존 코드 수정 없이, 타입 안전하게 할 수 있는가?"를 묻습니다.

  • SICP의 put/get 표는 런타임 딕셔너리입니다. 유연하지만 타입 태그가 틀리면 실행 중에 터지고, 컴파일러가 누락을 잡아주지 못합니다.
  • 하스켈 타입클래스는 같은 "연산×타입 → 구현" 매핑을 컴파일타임에 해결합니다. 인스턴스가 없으면 아예 빌드가 실패하므로, SICP 표의 "빈 칸" 버그가 원천 차단됩니다.

즉 하스켈은 SICP가 손으로 굴리던 디스패치 표를 언어 기능으로 승격시킨 셈입니다.
대신 방향이 갈립니다.
타입클래스는 새 타입 추가(인스턴스 하나)는 쉽지만, 새 연산 추가는 클래스 메서드를 건드려야 합니다.
반대로 대수적 데이터 타입(합 타입)에 패턴 매칭하는 방식은 새 연산 추가는 쉬운데 새 타입 추가가 번거롭습니다.
무엇을 자주 확장할지에 따라 골라야 합니다.

 

 

🛠 실무 적용 포인트

가산성을 코드로 체감해 봅시다.
새 도형을 추가할 때 기존 코드가 한 줄도 안 바뀌는 게 핵심입니다.

-- 제네릭 연산을 타입클래스로 선언 (SICP의 'get' 자리)
class Shape a where
  area :: a -> Double

data Circle = Circle Double
data Square = Square Double

-- 각 타입의 구현 = SICP의 'put' (표에 칸 채우기)
instance Shape Circle where
  area (Circle r) = pi * r * r

instance Shape Square where
  area (Square s) = s * s

-- ▼ 새 표현 추가: 기존 코드 수정 0, 인스턴스만 추가
data Triangle = Triangle Double Double
instance Shape Triangle where
  area (Triangle b h) = b * h / 2

반대로 "타입은 고정, 연산을 자주 늘린다"면 합 타입 + 패턴 매칭이 낫습니다.

data Shape = Circle Double | Square Double

area :: Shape -> Double            -- 연산 추가는 새 함수만 쓰면 끝
area (Circle r) = pi * r * r
area (Square s) = s * s

perimeter :: Shape -> Double       -- 기존 코드 수정 없이 연산 확장
perimeter (Circle r) = 2 * pi * r
perimeter (Square s) = 4 * s

정확도 참고: 하스켈에서 표현·구현을 완전히 분리하고 런타임 다형을 흉내 내려면 class에 더해 존재 타입(ExistentialQuantification)이나 GADTs가 필요할 수 있습니다.
세부 확장 문법은 GHC 공식 문서 확인을 권장합니다.

 

 

✅ 체크포인트

  • 제네릭 연산 설계 시, "타입을 자주 늘리나 vs 연산을 자주 늘리나" 먼저 판단하라. 전자는 타입클래스, 후자는 합 타입+패턴 매칭.
  • SICP의 put/get 표가 익숙하다면, 그 정신적 모델을 타입클래스 = 컴파일타임 디스패치 표로 바꿔 읽으면 하스켈이 훨씬 빨리 이해된다.
  • 런타임 문자열 태그로 디스패치하는 코드(자바스크립트 switch(type) 등)를 쓰고 있다면, 언어의 다형 기능으로 옮겨 누락을 컴파일러가 잡게 만들 여지가 있는지 점검해 보라.

 

 

📚 참고(출처)

 

 

📊 오늘의 개발 트렌드 (자동 집계)

GitHub·Hacker News·Hugging Face·App Store·Cloudflare Radar에서 매일 자동으로 모은 개발 트렌드입니다. (2026-07-14 18:43 기준)

🐙 GitHub 급상승

📰 Hacker News 화제

🤗 Hugging Face 트렌딩 모델

💰 App Store 매출 TOP (한국)

  • ChatGPT — OpenAI OpCo, LLC
  • YouTube — Google
  • 카카오톡 KakaoTalk — Daum Corp.

🌐 가장 많이 방문되는 웹 (Cloudflare Radar)

반응형