Haskell Reference
Haskell 프로그래밍 레퍼런스
Haskell Reference 소개
Haskell 레퍼런스는 순수 함수형 프로그래밍 언어인 Haskell에 대한 종합적이고 검색 가능한 가이드입니다. Haskell의 고유한 설계 철학을 반영하는 여섯 가지 카테고리로 구성됩니다: 기본문법(let/where 바인딩, 표현식으로서의 if-then-else, 가드, 패턴 매칭, case 표현식, 리스트 컴프리헨션, 리스트 연산, 람다 함수), 타입(타입 선언, data를 이용한 대수적 데이터 타입, 레코드 문법, newtype 래퍼, 타입 별칭), 함수(map/filter/foldr, (.)을 이용한 함수 합성, $ 적용 연산자, 커링, zipWith/concatMap), 모나드(Maybe, Either, do 표기법, >>= 바인드 연산자, return/pure), 타입클래스(class/instance 정의, Show/Eq/Ord/Enum/Bounded deriving, Functor/Applicative), IO(putStrLn/print, getLine/readLn, readFile/writeFile/appendFile, 가변 상태를 위한 IORef).
함수형 프로그래밍을 배우는 학습자, Servant나 Yesod 같은 라이브러리로 프로덕션 서비스를 구축하는 Haskell 실무자, 타입 이론과 범주론을 공부하는 컴퓨터 과학 학생이 주요 대상입니다. 이 레퍼런스는 실제 Haskell 코드 작업 시 옆에 두고 참조하기에 특히 유용합니다. Haskell의 문법은 명령형 언어와 크게 다르기 때문입니다: 모든 구문은 값을 반환하는 표현식이고, 함수는 기본적으로 커링되어 있으며, 모나드는 부수 효과, 선택적 값, 에러 전파를 예외 없이 구조적으로 처리하는 방법을 제공합니다.
레퍼런스는 다른 언어 배경의 개발자들이 흔히 어려워하는 Haskell 특유의 패턴을 중점적으로 다룹니다: 로컬 바인딩을 위한 let과 where의 차이, 깊게 중첩된 if-else 체인의 대안인 가드 문법, fmap과 <*>(Applicative)의 차이, do 표기법이 >>=(바인드) 체인으로 어떻게 변환되는지, IO 모나드 내에서 가변 상태를 위한 Haskell의 메커니즘인 IORef의 사용법. Data.IORef는 순수 함수형 자료구조와 함께 다루어 Haskell의 상태 관리에 대한 완전한 그림을 제공합니다.
주요 기능
- 패턴 매칭: 경우에 따른 함수 정의(factorial 0 = 1), (x:xs)를 이용한 리스트 분해, 언더스코어를 이용한 와일드카드
- 다중 조건을 위한 가드 문법(| x < 18.5 = ...)과 조건자를 포함한 리스트 컴프리헨션([x^2 | x <- [1..10], even x])
- data를 이용한 대수적 데이터 타입: 합 타입(Circle | Rectangle), 곱 타입, 이름 있는 필드를 가진 레코드 문법, deriving 절
- 타입 시스템 기능: 타입 선언(::), 제로 오버헤드 래퍼를 위한 newtype, 타입 별칭, ->를 이용한 함수 타입 시그니처
- 고차 함수: map, filter, foldr, foldl, zipWith, concatMap, 함수 합성(.)과 $ 적용 연산자
- 커링과 부분 적용: 모든 다중 인자 함수는 자동으로 커링됨; 부분 적용으로 새 함수 생성(예: add5 = add 5)
- 모나드 패턴: 안전한 연산 체이닝을 위한 Maybe, 메시지를 포함한 에러 전파를 위한 Either, 시퀀싱을 위한 do 표기법, 람다를 이용한 >>= 바인드
- 타입클래스: class/instance 정의, Show/Eq/Ord/Enum/Bounded 자동 deriving, 리프팅 연산을 위한 Functor(fmap)와 Applicative(<*>)
자주 묻는 질문
Haskell의 패턴 매칭이란 무엇이고 switch 문과 어떻게 다른가요?
Haskell의 패턴 매칭은 서로 다른 입력 형태에 각각 대응하는 여러 방정식으로 함수를 정의할 수 있게 합니다. 예를 들어 factorial 0 = 1과 factorial n = n * factorial (n-1)은 두 가지 경우를 정의합니다. switch 문과 달리 Haskell 패턴은 생성자(Just x, Nothing), 리스트 형태(x:xs, []), 튜플에 매칭할 수 있으며, 패턴이 모든 경우를 처리하지 않으면 컴파일러가 경고를 발생시킵니다.
가드란 무엇이고 if-then-else 대신 언제 사용하나요?
가드는 | 와 = 로 작성되는, 함수 절에 붙는 다중 조건 문법입니다. 세 가지 이상의 조건이 있을 때 중첩된 if-then-else보다 깔끔합니다. 예를 들어 | x < 18.5 = "저체중" | x < 25.0 = "정상" | otherwise = "과체중" 같은 BMI 분류기는 중첩된 if보다 훨씬 읽기 쉽습니다.
Maybe 모나드와 Either 모나드의 차이는 무엇인가요?
Maybe는 이유 없이 실패할 수 있는 연산을 표현합니다: 성공 시 Just x, 실패 시 Nothing. Either는 실패 메시지를 추가합니다: 성공 시 Right x, 실패 시 Left "에러 메시지". 실패 원인 설명이 필요 없을 때(예: 딕셔너리 조회)는 Maybe를, 호출자에게 설명적인 에러 메시지를 전달해야 할 때는 Either를 사용합니다.
do 표기법은 >>= 바인드 연산자와 어떻게 연관되나요?
do 표기법은 모나드 바인드(>>=)의 구문 설탕입니다. do 블록의 "x <- action" 줄은 "action >>= \x -> ..."로, 단독 "action" 줄은 "action >> ..."로 변환됩니다. IO 모나드에서 do 표기법은 내부적으로 순수 함수형을 유지하면서 명령형 코드처럼 읽힙니다.
Haskell에서 fmap과 <*>의 차이는 무엇인가요?
fmap(Functor 연산)은 순수 함수를 context 안으로 리프팅합니다: fmap (+1) (Just 5) = Just 6. <*>(Applicative 연산)은 context 안에 있는 함수를 context 안에 있는 값에 적용합니다: Just (+3) <*> Just 5 = Just 8. fmap은 매핑으로, <*>는 박스 안에서의 함수 적용으로 생각하면 됩니다.
Haskell 타입클래스란 무엇이고 OOP 인터페이스와 어떻게 다른가요?
타입클래스는 타입이 지원해야 하는 연산 집합을 정의하며, OOP의 인터페이스와 유사합니다. 하지만 더 강력합니다: 기존 타입을 수정하지 않고도 타입클래스 인스턴스를 추가할 수 있으며(소급 구현), 컴파일러가 타입에 따라 컴파일 타임에 올바른 구현을 선택합니다. 일반적인 타입클래스에는 Show(문자열 변환), Eq(동등성), Ord(순서), Functor(매핑)가 있습니다.
Haskell이 순수 함수형이라면 가변 상태는 어떻게 처리하나요?
Haskell의 순수 함수는 부수 효과를 가질 수 없지만, IO 모나드는 I/O 수행과 가변 상태 관리를 위한 통제된 방법을 제공합니다. IORef는 표준 가변 참조 타입입니다: newIORef 0으로 참조를 생성하고, modifyIORef ref (+1)으로 갱신하고, readIORef ref로 읽습니다. 이 연산들은 모두 IO 액션을 반환하여 타입 시스템이 변이를 추적하도록 합니다.
커링이란 무엇이고 Haskell에서 부분 적용은 어떻게 동작하나요?
Haskell에서 여러 인자를 받는 모든 함수는 자동으로 커링됩니다 — 실제로는 하나의 인자를 받고 다음 인자를 받는 함수를 반환합니다. 이는 기대보다 적은 인자를 제공하여 함수를 부분 적용할 수 있음을 의미합니다: add5 = add 5는 어떤 수에든 5를 더하는 새 함수를 생성합니다. map (add 10) [1,2,3]은 부분 적용을 사용하여 각 리스트 원소에 10을 더합니다.