💻 프로그래밍

Haskell

순수 함수형 프로그래밍 언어

📖 상세 설명

Haskell은 1990년에 학술 및 산업 연구를 위한 표준 순수 함수형 프로그래밍 언어로 설계되었습니다. 수학자이자 논리학자인 Haskell Curry의 이름을 따서 명명되었으며, 람다 대수와 수학적 엄밀함을 기반으로 합니다.

Haskell의 가장 큰 특징은 지연 평가(Lazy Evaluation)입니다. 표현식은 실제로 값이 필요할 때까지 계산되지 않으며, 이를 통해 무한 데이터 구조를 정의하고 불필요한 계산을 회피할 수 있습니다. 또한 Hindley-Milner 타입 추론 시스템으로 타입 선언 없이도 컴파일러가 타입을 자동 추론합니다.

모나드(Monad)는 Haskell의 핵심 개념으로, 순수 함수형 언어에서 부수 효과(I/O, 상태 변경 등)를 안전하게 다루는 방법을 제공합니다. IO 모나드, Maybe 모나드, Either 모나드 등이 있으며, do 표기법으로 명령형 스타일처럼 작성할 수 있습니다.

실무에서 Haskell은 금융 분야(정확한 수치 계산), 컴파일러 개발(GHC 자체가 Haskell로 작성됨), 블록체인(Cardano), 그리고 정적 분석 도구에서 활용됩니다. Facebook의 스팸 필터링 시스템 Sigma도 Haskell로 작성되어 하루 수십억 건의 요청을 처리합니다.

💻 코드 예제

-- 1. 기본 함수 정의와 패턴 매칭
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

-- 가드(Guard)를 사용한 조건 분기
grade :: Int -> String
grade score
    | score >= 90 = "A"
    | score >= 80 = "B"
    | score >= 70 = "C"
    | otherwise   = "F"

-- 2. 리스트와 고차 함수
numbers :: [Int]
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

-- 짝수만 필터링하고 제곱한 후 합계
sumOfEvenSquares :: Int
sumOfEvenSquares = sum $ map (^2) $ filter even numbers
-- 결과: 220 (4 + 16 + 36 + 64 + 100)

-- 리스트 컴프리헨션
pythagoreanTriples :: [(Int, Int, Int)]
pythagoreanTriples = [(a,b,c) | c <- [1..20],
                                b <- [1..c],
                                a <- [1..b],
                                a^2 + b^2 == c^2]
-- [(3,4,5), (6,8,10), (5,12,13), ...]

-- 3. 지연 평가와 무한 리스트
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

-- 처음 10개의 피보나치 수 (무한 리스트에서 가져옴)
firstTenFibs :: [Integer]
firstTenFibs = take 10 fibs
-- [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

-- 4. Maybe 모나드를 이용한 안전한 연산
safeDivide :: Double -> Double -> Maybe Double
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)

-- do 표기법으로 모나드 연쇄
calculate :: Double -> Double -> Double -> Maybe Double
calculate a b c = do
    result1 <- safeDivide a b
    result2 <- safeDivide result1 c
    return (result2 * 2)

-- 5. 타입 클래스와 다형성
class Describable a where
    describe :: a -> String

data Person = Person { name :: String, age :: Int }

instance Describable Person where
    describe p = name p ++ ", " ++ show (age p) ++ "세"

-- 사용 예시
main :: IO ()
main = do
    putStrLn $ "5! = " ++ show (factorial 5)  -- 120
    putStrLn $ "Grade: " ++ grade 85          -- B
    print firstTenFibs
    print $ describe (Person "홍길동" 25)      -- "홍길동, 25세"

🗣️ 실무에서 이렇게 말하세요

💬 회의에서
"이 결제 로직은 금액 계산 정확성이 중요해서 Haskell로 작성하는 게 좋겠습니다. 순수 함수로 작성하면 동일 입력에 항상 동일 결과가 보장되고, 타입 시스템으로 잘못된 연산을 컴파일 시점에 잡을 수 있어요."
💬 면접에서
"Haskell의 모나드는 컨텍스트가 있는 계산을 추상화한 개념입니다. Maybe 모나드는 실패 가능성을, IO 모나드는 부수 효과를 타입으로 표현해서 순수 함수와 불순 함수를 명확히 분리합니다. 이를 통해 코드의 예측 가능성과 테스트 용이성이 크게 높아집니다."
💬 코드 리뷰에서
"이 재귀 함수가 꼬리 재귀가 아니라서 스택 오버플로우 위험이 있어요. 지연 평가 때문에 thunk가 쌓일 수 있으니, seq이나 BangPatterns를 사용해서 엄격 평가로 바꾸거나 foldl' 같은 엄격한 버전을 사용해주세요."

⚠️ 흔한 실수 & 주의사항

공간 누수(Space Leak) 주의

지연 평가로 인해 thunk(지연된 계산)가 메모리에 쌓여 공간 누수가 발생할 수 있습니다. foldl 대신 foldl'(엄격 버전)을 사용하고, 필요시 seq이나 deepseq로 평가를 강제하세요.

모나드 변환자 스택 복잡성

여러 모나드를 조합할 때 ReaderT, StateT, ExceptT 등을 쌓으면 타입이 매우 복잡해집니다. mtl 패턴이나 효과 시스템(effect system)을 고려하고, 타입 별칭을 적극 활용하세요.

타입 주도 개발(Type-Driven Development) 활용

Haskell의 강력한 타입 시스템을 적극 활용하세요. 타입을 먼저 정의하고 구현하면 컴파일러가 많은 버그를 잡아줍니다. "Make illegal states unrepresentable" 원칙을 따라 잘못된 상태 자체가 표현 불가능하도록 타입을 설계하세요.

🔗 관련 용어

📚 더 배우기