콘텐츠로 바로가기

타입 시스템과 정적 분석 (Type Systems & Static Analysis)

타입 시스템과 정적 분석이 실행 전 프로그램 성질을 검증하는 방식을 정리한 언어 이론 학습 노드입니다.

Article
M

Me

hyunyoun's Blog

programming-languages-compilersprogramming-languagescompilerslanguage-theorytype-systemsstatic-analysislanguages-compilerslearning9 min read

1. Overview

타입 시스템과 정적 분석(Type Systems & Static Analysis)은 코드를 실행하기 전에, 잘못된 타입 사용(정수를 문자열처럼 더하기, null 역참조, 타입 불일치)을 컴파일러가 수학적으로 탐지하여 런타임 오류의 여러 유형을 줄이는 프로그래밍 언어 설계의 핵심 보호막입니다.

학습자는 정적 타입(Statically Typed) vs 동적 타입(Dynamically Typed), 강 타입(Strongly Typed) vs 약 타입(Weakly Typed)의 2×2 차원을 정확히 구분하고, Hindley-Milner 타입 추론(Type Inference) 알고리즘이 어떻게 타입 어노테이션 없이도 전체 프로그램의 타입을 자동 추론하는지 살펴봅니다. 나아가 다형성(Polymorphism, Generic/Parametric/Ad-hoc), 서브타입(Subtyping) vs 구조적 타입(Structural Typing), **null 안전성(Null Safety)**의 현대 타입 시스템 설계 원칙을 분석하여, Kotlin/Rust/Haskell의 설계 철학을 이해하는 역량을 확보합니다.

2. Scope & Boundaries

In-Scope

  • 타입 시스템 분류 (Type System Taxonomy): 정적 vs 동적, 강 vs 약, 명시적(Manifest) vs 추론(Inferred), 점진적 타입(Gradual Typing).
  • 타입 추론 (Type Inference): Hindley-Milner 타입 추론, 단일화(Unification), 일반화(Generalization)/구체화(Instantiation).
  • 다형성 (Polymorphism): 매개변수 다형성(Parametric, Generics), 서브타입 다형성(Subtype, OOP), 임시 다형성(Ad-hoc, Overloading/Typeclass).
  • 현대 타입 기능 (Modern Type Features): Null Safety(Kotlin/Swift), Algebraic Data Types(Rust enum, Haskell ADT), 의존 타입(Dependent Types, 간략 개요).

Out-of-Scope

  • 형식 검증(Formal Verification): Hoare Logic, Coq/Isabelle 정리 증명 → 05-01-01 Grammar & Semantics 심화 영역.
  • 런타임 리플렉션(Runtime Reflection): 실행 시 타입 정보 조작 → 각 언어 런타임 영역.

Boundaries

  • 정적 타입 vs 동적 타입의 실용적 차이: 정적 타입(Java, Rust, C++)은 많은 타입 오류를 컴파일 시점에 탐지하여 대규모 팀 프로젝트에서 리팩토링 안전성을 제공하지만, 타입 어노테이션 작성 부담이 있습니다. 동적 타입(Python, JavaScript)은 빠른 프로토타이핑이 장점이지만 런타임 TypeError가 Production에서 발생할 수 있습니다. TypeScript/mypy 같은 점진적 타입(Gradual Typing)은 중간 지점을 제공합니다.

3. Counterexample

  • null 포인터의 10억 달러 실수 (The Billion Dollar Mistake): Tony Hoare가 1965년 ALGOL에 null 참조를 도입한 것을 "내 10억 달러짜리 실수"로 인정한 유명한 고백입니다. Java에서 String s = null; s.length() → NullPointerException이 실제 서버에서 흔한 런타임 크래시가 될 수 있습니다. Kotlin의 String?(nullable)와 String(non-nullable) 구분, Rust의 Option<T>(None | Some(x))는 null 참조 문제를 컴파일 타임에 다루게 만듭니다.
  • 암묵적 형변환의 함정 (JavaScript Type Coercion): JavaScript(약 타입)에서 "5" + 3 = "53" (문자열 연결), "5" - 3 = 2 (숫자 뺄셈). 같은 +- 연산자가 타입에 따라 전혀 다른 의미로 동작하는 암묵적 형변환(Implicit Coercion)은 "" == false → true, null == undefined → true처럼 직관과 다른 결과를 만들 수 있습니다. TypeScript의 정적 타입이 널리 쓰이게 된 이유도 여기에 있습니다.

4. Prerequisites

  • 함수와 변수의 개념 (Basic): 타입이 어떤 연산이 허용되는지를 정의하는 "값의 분류"임을 이해해야 합니다.
  • 문법과 파스 트리 (Recommended): 타입 추론이 AST를 순회하며 타입 방정식을 세우는 과정 이해에 필요합니다. (05-01-01 Grammar & Semantics)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 Type System Taxonomy 정적/동적, 강/약, 구조적/지명적 타입 시스템의 2×2 분류와 언어별 위치를 이해합니다. P1
2 Hindley-Milner Type Inference 타입 어노테이션 없이도 map f [] = []의 타입을 (a→b) → [a] → [b]로 추론하는 HM 알고리즘을 살펴봅니다. P5
3 Parametric & Subtype Polymorphism List<T>의 제네릭(매개변수 다형성)과 OOP의 Dog extends Animal (서브타입)이 각각 어떻게 타입 안전성을 보장하는지 비교합니다. Industry
4 Null Safety & ADT Kotlin String?, Rust Option<T>, Haskell ADT로 null 오류를 컴파일 타임에 다루는 현대 설계를 이해합니다. Industry

6. Learning Topics

Basic

Core Topic 01: 타입 시스템의 4가지 차원, 분류와 언어별 위치 (Type System Taxonomy)

  • Why to Learn: "Python은 왜 타입을 안 써도 돼요?" vs "Rust는 왜 이렇게 타입이 엄격해요?"라는 차이가 정적/동적·강/약의 2차원 좌표계와 연결됨을 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: 정적(Static, 컴파일 타임 타입 검사) vs 동적(Dynamic, 런타임 검사), 강(Strong, 암묵적 형변환 없음) vs 약(Weak, 자유로운 변환), 구조적 타입(Structural, Duck Typing) vs 지명적 타입(Nominal, 이름 기반).
    • Skills: 언어별 타입 시스템 위치 분류(Java=정적/강, Python=동적/강, JavaScript=동적/약, C=정적/약, TypeScript=정적/강, Go=정적/강/구조적).
  • How to Learn:
    • 1단계: 정적: Java int x = "hello" → 컴파일 오류. 동적: Python x = "hello"; x = 42 → 런타임 OK. 강: Python "5" + 3TypeError. 약: JavaScript "5" + 3"53". 4가지 케이스의 언어 동작 차이를 비교합니다.
    • 2단계: 구조적 타입(Go/TypeScript): 인터페이스에 필요한 메서드를 구현하면 자동으로 해당 인터페이스 타입. type Quacker interface { Quack() } → Quack()을 가진 Duck, Person 모두 Quacker. 지명적(Java): implements Quacker를 명시해야 합니다.
  • Implement: 파이썬 type_system_demo.py. 파이썬 runtime_type_check 데코레이터: 함수 인자 타입 어노테이션(Python 3.x def add(x: int, y: int) -> int:)을 검사하여 불일치 시 TypeError 발생. 동적 타입 언어에 정적 타입 강제 레이어 추가 효과 데모.

Core Topic 02: 어노테이션 없이 타입을 알아내다, HM 타입 추론 (Hindley-Milner Inference)

  • Why to Learn: Haskell, ML, 최신 Rust/Swift가 타입 어노테이션 없이도 프로그램의 타입을 추론하는 Hindley-Milner 알고리즘의 핵심인 단일화(Unification)와 일반화(Generalization)를 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: 타입 변수(Type Variable, α, β), 타입 방정식(Type Equation), 단일화(Unification, 방정식 풀기), 일반화(Generalization, ∀α.α→α), 구체화(Instantiation).
    • Skills: 간단 표현식의 타입 추론 수동 계산, let-polymorphism.
  • How to Learn:
    • 1단계: let id = λx.x 함수의 타입 추론. x에 신선한 타입 변수 α를 부여. id : α → α. ∀α. α → α로 일반화하면 id 5 : Int, id "hello" : String 모두 성공하는 다형 함수가 됩니다.
    • 2단계: let fst = λ(x,y).x 타입 추론. (x:α, y:β) → α. 일반화: ∀αβ. (α,β) → α. fst (1, True) : Int, fst ("hi", 3.14) : String 구체화 검증을 살펴봅니다.
  • Implement: 파이썬 미니 타입 추론기. TVar(name), TFun(t1, t2), TInt, TBool 타입 노드. unify(t1, t2) → 치환(Substitution) 딕셔너리 반환. infer(expr, env) → 타입 추론. App(Lam('x', Var('x')), Num(5))TInt 추론 검증.

Practical

Core Topic 03: 하나의 코드로 다양한 타입, 다형성의 3가지 얼굴 (Polymorphism)

  • Why to Learn: List<T> 제네릭, Animal 서브타입, 오버로딩/Typeclass의 세 가지 다형성이 각각 어떤 타입 안전성 보장을 제공하며, Java의 제네릭 타입 소거(Type Erasure)가 실무 버그로 이어질 수 있는 이유를 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: 매개변수 다형성(Parametric, Java <T>, Haskell forall a), 서브타입 다형성(Subtype, Liskov Substitution Principle), 임시 다형성(Ad-hoc, Overloading, Haskell Typeclass, Rust trait), 공변/반변(Covariance/Contravariance).
    • Skills: Liskov 치환 원칙(LSP) 검증, Java 제네릭 타입 소거(Type Erasure) 한계.
  • How to Learn:
    • 1단계: 매개변수 다형성: def length<T>(list: List<T>): Int. 어떤 타입 T의 리스트도 길이 계산 가능. Java에서 List<Integer>List<String>이 런타임에 동일하게 List(타입 소거)가 되는 문제와, Haskell [a]가 런타임에도 타입 정보를 유지하는 차이를 분석합니다.
    • 2단계: Liskov 치환 원칙(LSP): Animal a = new Dog() → Dog가 Animal처럼 행동해야 합니다. Dog의 speak() 메서드가 Animal의 계약을 위반하면(예: 예외 추가, 반환 타입 좁힘) LSP 위반으로 다형성 버그가 발생하는 과정을 살펴봅니다.
  • Implement: 파이썬 타입 안전 제네릭 스택 Stack[T]. from typing import Generic, TypeVar T=TypeVar('T'). push(x: T), pop() -> T. Stack[int]push("string") 시 mypy 정적 검사 오류 vs 런타임 OK를 비교하는 정적 분석 vs 런타임 차이 데모.

Advanced

Core Topic 04: null의 제거와 합 타입, 현대 타입 시스템 (Null Safety & ADT)

  • Why to Learn: Kotlin의 String?, Rust의 Option<T>, Haskell의 Maybe a가 null 포인터 예외(NPE)를 컴파일 타임에 다루게 만들고, ADT(대수적 데이터 타입)가 비즈니스 도메인 오류를 타입으로 인코딩하는 현대 설계 철학을 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: Nullable 타입(String?), Option<T>/Maybe a(None/Some 패턴 매칭), ADT(Sum Type = Enum, Product Type = Struct), 패턴 매칭(Pattern Matching) 완전성 검사(Exhaustiveness Check).
    • Skills: match 표현식으로 모든 케이스 강제 처리, ? 연산자(Rust), ?. 안전 호출(Kotlin).
  • How to Learn:
    • 1단계: Rust Option<T>: fn find_user(id: u32) -> Option<User>. 반환값을 match result { Some(u) => ..., None => ... }로 반드시 처리. None 케이스를 빠뜨리면 컴파일 오류. Java Optional<User>.get() 무조건 호출 가능해 Optional의 안전성 보장이 약해질 수 있음을 비교합니다.
    • 2단계: ADT로 결과 타입 인코딩. enum Result<T, E> { Ok(T), Err(E) }. parse_int("42") -> Ok(42), parse_int("abc") -> Err("not a number"). 패턴 매칭으로 Ok/Err를 반드시 처리하는 오류 처리 방식을 살펴봅니다.
  • Implement: 파이썬 Option, Result ADT 구현. class Some(Generic[T]), class Nothing. safe_divide(a, b) -> Option[float]: b=0이면 Nothing, 아니면 Some(a/b). Result 클래스로 parse_json(s) -> Result[dict, str]. match 패턴(파이썬 3.10+) 또는 isinstance로 완전 처리 강제하는 안전 API 데모.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
Type System 프로그램의 값과 표현식을 '타입(Type)'이라는 집합으로 분류하여, 무의미하거나 위험한 연산을 실행 전에 차단하는 논리적 방어 체계입니다. 기본 언어 안전성 Static/Dynamic Typing Type Checking 타입 검사가 모든 런타임 버그를 막아주는 것은 아님 P1:CS2023 core
Static Analysis 소스 코드를 실행(Run)하지 않고 AST나 제어 흐름 그래프(CFG)를 수학적으로 추적하여 버그, 타입 에러, 보안 취약점을 찾아내는 검증 기법입니다. 권장 버그 사전 탐지 Type Inference / Linter Dynamic Analysis 프로그램의 모든 실행 경로를 완전히 예측할 수는 없음 (정지 문제) P5:SFIA core
Type Inference 개발자가 변수의 타입을 명시적으로 적지 않아도, 컴파일러가 주변 문맥과 연산자를 바탕으로 변수의 타입을 수학적으로 역추적(추론)하여 확정하는 기술입니다. 실무 생산성 향상 Hindley-Milner Explicit Typing 동적 타이핑이 아니라, 컴파일 타임에 정적 타입을 '자동 결정'하는 것임 Industry core
Soundness '타입 시스템이 유효하다고 판정한 프로그램은 런타임에 타입 오류로 실패하지 않는다'는 수학적 보장성입니다. 심화 타입 이론 Completeness Unsoundness Soundness를 엄격히 만족하면 올바른 프로그램도 거절당할 수 있음 Industry core

8. References

Primary

  • [P1] CS2023 - Programming Languages (PL) - Type Systems
  • [P5] SFIA - Software Development (PROG) - Static Analysis

Secondary

  • [Types and Programming Languages] Benjamin C. Pierce - Type Systems and Soundness proofs
  • [Principles of Program Analysis] Flemming Nielson - Data Flow Analysis

Industry

  • [TypeScript Handbook] - Type Compatibility, Structural Typing, and Inference
  • [SonarQube Documentation] - Static Code Analysis and Control Flow Graph rules

9. Final Checklist

Primary

  • 정적 타이핑(Static)과 동적 타이핑(Dynamic)의 차이를 실행 전 검증과 실행 중 타입 태그 검사 관점에서 설명할 수 있는가?
  • 강타이핑(Strong)과 약타이핑(Weak)의 차이가 메모리 바이트 해석의 엄격함(암묵적 형변환 허용 여부)에 있음을 논증할 수 있는가?

Secondary

  • 서브타이핑(Subtyping)과 다형성(Polymorphism)이 객체 지향이나 제네릭(Generic) 프로그래밍에서 코드 재사용성과 타입 안전성을 어떻게 동시에 확보하는지 설명할 수 있는가?
  • 정적 분석기(Linter/Analyzer)가 제어 흐름 그래프(CFG)를 구축하여 도달할 수 없는 코드(Dead Code)나 널 포인터 참조 가능성을 역추적하는 메커니즘을 증명할 수 있는가?

Industry

  • 타입스크립트(TypeScript)나 고(Go)에서 쓰이는 구조적 타이핑(Structural Typing/Duck Typing)이 이름 기반 타이핑(Nominal Typing) 대비 분산 마이크로서비스 설계에 주는 이점을 설계할 수 있는가?
  • 튜링 완전한 언어에서 정적 분석기만으로는 모든 버그를 잡을 수 없는 한계(정지 문제)를 인지하고, 이를 동적 테스트와 어떻게 상호 보완할지 아키텍처링 할 수 있는가?

Languages Compilers · Language Theory & Type Systems

3 / 5