{
  "title": "타입 추론과 제약 해결",
  "description": "Hindley–Milner 단일화, Rust 라이프타임 추론, 트레잇·타입클래스 해석 — 정적 의미론이 어떻게 '비자명한 ∃(존재 판정)'를 품는지 봅니다.",
  "languageCode": "ko",
  "cards": [
    {
      "type": "ox",
      "prompt": "타입 추론(type inference)은 프로그래머가 타입을 명시하지 않아도 컴파일러가 식의 타입을 추론해내는 정적 분석이다.",
      "answer": true,
      "explanation": "타입 추론은 실행 없이 컴파일 시점에 이뤄지는 정적 의미론의 일부입니다."
    },
    {
      "type": "choice",
      "prompt": "Hindley–Milner 타입 추론에서 타입 제약을 푸는 핵심 절차는?",
      "choices": [
        "단일화(unification)",
        "가비지 컬렉션",
        "JIT 컴파일",
        "페이지 교체"
      ],
      "answer": 1,
      "explanation": "HM은 타입 변수 사이의 제약을 단일화로 풀어 주(principal) 타입을 찾습니다."
    },
    {
      "type": "ox",
      "prompt": "타입 추론은 본질적으로 '식이 타입 검사를 통과하게 하는 타입 배정이 존재하는가?'라는 존재(∃) 문제로 볼 수 있다.",
      "answer": true,
      "explanation": "즉 ∃(타입 배정). 잘-타입됨(well-typed) — 정적 의미론에 자리한 존재 판정입니다."
    },
    {
      "type": "ox",
      "prompt": "Rust의 대여 검사기(borrow checker)는 참조의 라이프타임을 상당 부분 추론하며, 이는 제약 조건을 푸는 과정에 해당한다.",
      "answer": true,
      "explanation": "라이프타임 제약을 만족하는 해가 존재하는지 판정·추론합니다(NLL 등). 정적 의미론의 ∃ 사례입니다."
    },
    {
      "type": "ox",
      "prompt": "Haskell의 타입 클래스나 Rust의 트레잇 해석(trait resolution)은 '적절한 인스턴스·구현이 존재하는가'를 찾는 일종의 증명 탐색이다.",
      "answer": true,
      "explanation": "인스턴스/구현 탐색은 규칙에 따른 프루프 서치입니다 — 비자명한 ∃의 전형입니다."
    },
    {
      "type": "choice",
      "prompt": "다음 중 '정적 의미론에서의 비자명한 ∃'로 보기 어려운 것은?",
      "choices": [
        "지역 변수의 이름이 존재한다는 사실",
        "여러 후보 중 맞는 타입을 찾아야 하는 타입 추론",
        "라이프타임 제약을 만족하는 해가 존재하는지 판정",
        "트레잇 구현을 규칙에 따라 탐색"
      ],
      "answer": 1,
      "explanation": "이름의 존재는 자명한 ∃입니다. 나머지는 탐색·추론이 필요한 비자명한 ∃입니다."
    },
    {
      "type": "ox",
      "prompt": "일반적으로 타입 클래스·트레잇의 인스턴스 탐색은 규칙에 따라 무한히 확장될 수 있어, 이론적으로 결정 불가능(undecidable)해질 수도 있다.",
      "answer": true,
      "explanation": "그래서 언어들은 종료를 보장하려 다양한 제약(coherence·overlap 규칙 등)을 둡니다."
    },
    {
      "type": "text",
      "prompt": "HM 타입 시스템에서 두 타입 식을 같게 만드는 대입을 찾는 연산을 무엇이라 하는가?",
      "answers": [
        "단일화",
        "unification",
        "유니피케이션"
      ],
      "explanation": "단일화(unification)입니다. 타입 변수에 대입을 찾아 제약을 만족시킵니다."
    },
    {
      "type": "ox",
      "prompt": "타입 추론이 존재(∃) 판정을 포함하기만 하면, 그 언어의 실행 의미론도 자동으로 선언형이 된다.",
      "answer": false,
      "explanation": "이건 '정적 의미론의 ∃'일 뿐입니다. 실행 의미론까지 선언형이 되려면 다른 층위의 논증이 필요합니다(다음 세트)."
    },
    {
      "type": "ox",
      "prompt": "Rust에서 안전한 코드의 데이터 경합은 대체로 타입 시스템(소유권·대여) 수준에서 정적으로 방지된다.",
      "answer": true,
      "explanation": "Send/Sync와 소유권·대여 규칙이 안전한 코드의 데이터 경합을 컴파일 시점에 막습니다."
    },
    {
      "type": "ox",
      "prompt": "타입 추론은 프로그램을 실제로 실행해봐야만 가능한 동적 분석이다.",
      "answer": false,
      "explanation": "타입 추론은 실행 없이 컴파일 시점에 수행되는 정적 분석입니다."
    },
    {
      "type": "ox",
      "prompt": "Rust의 라이프타임은 언제나 프로그래머가 손으로 전부 명시해야 하며, 컴파일러가 추론하지 않는다.",
      "answer": false,
      "explanation": "많은 경우 라이프타임은 생략(elision)·추론됩니다. 필요한 곳에서만 명시합니다."
    },
    {
      "type": "ox",
      "prompt": "Hindley–Milner 타입 시스템은 잘-타입된 모든 식에 대해 주 타입(principal type)이 존재함을 보장한다.",
      "answer": true,
      "explanation": "주 타입은 그 식이 가질 수 있는 다른 모든 타입이 인스턴스로 얻어지는 가장 일반적인 타입입니다. 이 성질 덕분에 표기 없이도 컴파일러가 '가장 좋은' 타입 하나를 정할 수 있습니다."
    },
    {
      "type": "ox",
      "prompt": "HM에서는 람다로 묶인 인자도 함수 본문 안에서 서로 다른 타입으로 다형적으로 사용할 수 있다.",
      "answer": false,
      "explanation": "HM의 다형성은 let-다형성입니다. 람다 인자는 단형(monomorphic)으로 다뤄지고, let으로 묶인 이름만 일반화되어 여러 타입으로 쓰일 수 있습니다."
    },
    {
      "type": "choice",
      "prompt": "HM 타입 추론에서 타입 변수의 일반화(generalization, ∀ 도입)가 일어나는 지점은?",
      "choices": [
        "람다 인자를 도입할 때",
        "함수를 호출할 때",
        "let 바인딩을 할 때",
        "if 분기를 합칠 때"
      ],
      "answer": 3,
      "explanation": "let 바인딩에서 환경에 자유롭지 않은 타입 변수를 ∀로 묶어 다형 타입 스킴을 만듭니다. 그래서 HM의 다형성을 let-다형성이라 부릅니다."
    },
    {
      "type": "ox",
      "prompt": "단일화 도중 타입 변수 α를 α가 포함된 타입에 대입하려는 시도를 막는 검사를 occurs check라 하며, 무한 타입이 생기는 것을 방지한다.",
      "answer": true,
      "explanation": "α = α → β 같은 제약은 유한한 해가 없어 실패해야 합니다. occurs check를 빼면 순환 구조가 만들어져 추론이 발산할 수 있습니다."
    },
    {
      "type": "ox",
      "prompt": "두 타입 식을 단일화하는 대입이 존재한다면, 그런 대입은 언제나 정확히 하나뿐이다.",
      "answer": false,
      "explanation": "보통 여러 대입이 존재하며, 그중 다른 모든 해가 인스턴스로 얻어지는 최일반 단일자(mgu)가 유일하게(변수 이름 바꾸기 차이를 빼면) 존재합니다. 추론은 이 mgu를 취합니다."
    },
    {
      "type": "ox",
      "prompt": "HM의 다형성은 랭크-1(prenex)로 제한되어, ∀가 타입의 맨 바깥에만 올 수 있다.",
      "answer": true,
      "explanation": "그래서 (∀a. a → a) → Int 처럼 인자 위치에 ∀가 들어간 랭크-2 타입은 HM으로 표현·추론되지 않습니다. 이런 타입을 쓰려면 언어 확장과 명시적 표기가 필요합니다."
    },
    {
      "type": "text",
      "prompt": "어떤 식이 가질 수 있는 다른 모든 타입이 그것의 인스턴스로 얻어지는, 가장 일반적인 타입을 무엇이라 하는가?",
      "answers": [
        "주 타입",
        "주타입",
        "principal type",
        "principal",
        "주요 타입"
      ],
      "explanation": "주 타입(principal type)입니다. HM은 잘-타입된 식마다 주 타입이 존재하고 알고리즘이 그것을 찾아냄을 보장합니다."
    },
    {
      "type": "ox",
      "prompt": "타입 시스템이 강력해져도 완전한 타입 추론은 언제나 결정 가능하게 유지된다.",
      "answer": false,
      "explanation": "System F(전면 다형 람다 계산)의 타입 추론은 결정 불가능함이 증명되어 있습니다(Wells). 그래서 실용 언어는 HM처럼 추론 가능한 부분집합을 쓰거나 표기를 요구합니다."
    },
    {
      "type": "ox",
      "prompt": "다형적 재귀(polymorphic recursion)를 허용하면 타입 추론이 결정 불가능해지므로, Haskell은 그런 정의에 타입 시그니처를 명시하도록 요구한다.",
      "answer": true,
      "explanation": "재귀 호출에서 자기 자신을 다른 타입으로 쓰는 경우의 추론은 결정 불가능합니다. 시그니처를 주면 추론이 아니라 검사 문제로 바뀌어 처리할 수 있습니다."
    },
    {
      "type": "choice",
      "prompt": "부분타입이 있는 언어에서 함수 타입 A → B의 변성(variance)으로 옳은 것은?",
      "choices": [
        "인자 위치는 반변(contravariant), 결과 위치는 공변(covariant)",
        "인자 위치는 공변, 결과 위치는 반변",
        "두 위치 모두 공변",
        "두 위치 모두 불변(invariant)"
      ],
      "answer": 1,
      "explanation": "인자 위치는 반변, 결과 위치는 공변입니다. 더 넓은 입력을 받고 더 좁은 출력을 내는 함수는 언제든 원래 함수 자리에 안전하게 들어갈 수 있기 때문입니다."
    },
    {
      "type": "ox",
      "prompt": "자바의 배열은 원소 타입에 대해 공변이며, 그 공변성은 정적으로도 완전히 안전하다.",
      "answer": false,
      "explanation": "자바 배열은 공변이지만 그 공변성은 불건전합니다. 그래서 잘못된 원소를 넣는 코드가 컴파일은 통과하고 실행 중 ArrayStoreException으로 잡힙니다."
    },
    {
      "type": "ox",
      "prompt": "HM 타입 추론은 최악의 경우 프로그램 크기에 대해 지수적인 시간이 걸릴 수 있다.",
      "answer": true,
      "explanation": "중첩된 let이 타입을 겹겹이 키우면 최악의 경우가 발생하며, HM 타입 판정은 DEXPTIME-완전으로 알려져 있습니다. 다만 사람이 쓰는 실제 코드에서는 대체로 매우 빠릅니다."
    },
    {
      "type": "ox",
      "prompt": "점진적 타이핑(gradual typing)에서 동적 타입 ?(Dyn)을 다루는 일관성(consistency) 관계는 부분타입 관계처럼 추이적(transitive)이다.",
      "answer": false,
      "explanation": "일관성은 반사적·대칭적이지만 추이적이지 않습니다. Int ~ ?, ? ~ Bool 이어도 Int ~ Bool은 성립하지 않으며, 이 비추이성이 ?가 타입 검사를 무너뜨리지 않게 하는 핵심입니다."
    },
    {
      "type": "ox",
      "prompt": "점진적 타입 시스템은 정적 영역과 동적 영역의 경계에 런타임 캐스트를 넣고, 실패 시 어느 쪽 책임인지를 가리키는 blame을 보고한다.",
      "answer": true,
      "explanation": "경계에서 삽입된 캐스트가 값이 표기와 맞는지 실행 중 확인합니다. blame 추적 덕분에 오류를 실제로 계약을 어긴 코드 쪽에 귀속시킬 수 있습니다."
    },
    {
      "type": "ox",
      "prompt": "타입 추론이 있는 언어에서는 타입 표기를 전혀 쓸 필요가 없다.",
      "answer": false,
      "explanation": "추론 범위를 벗어나는 곳(다형적 재귀, 랭크-N 다형성 등)에서는 표기가 필수입니다. 게다가 최상위 시그니처는 문서·오류 국소화 목적으로도 권장됩니다."
    },
    {
      "type": "choice",
      "prompt": "양방향 타입 검사(bidirectional typing)가 나누는 두 가지 판단은?",
      "choices": [
        "파싱(parsing)과 링킹(linking)",
        "일반화(generalization)와 인스턴스화(instantiation)",
        "공변(covariance)과 반변(contravariance)",
        "검사(checking)와 합성(synthesis)"
      ],
      "answer": 4,
      "explanation": "기대 타입을 받아 맞는지 보는 검사(checking)와, 식에서 타입을 뽑아내는 합성(synthesis)으로 규칙을 나눕니다. 이 분리 덕분에 표기를 조금만 붙여도 강력한 타입 시스템을 다룰 수 있습니다."
    },
    {
      "type": "ox",
      "prompt": "ML의 값 제한(value restriction)은 let 우변이 구문적 값일 때만 일반화를 허용하여, 가변 참조와 다형성이 겹칠 때 생기는 불건전성을 막는다.",
      "answer": true,
      "explanation": "빈 참조 셀에 다형 타입을 주면 한 타입으로 넣고 다른 타입으로 꺼내는 코드가 통과해 버립니다. 값 제한은 그런 표현식의 일반화를 금지해 이를 차단합니다."
    },
    {
      "type": "ox",
      "prompt": "HM에 부분타입(subtyping)을 그대로 추가해도 주 타입 성질과 완전한 추론이 아무 대가 없이 유지된다.",
      "answer": false,
      "explanation": "부분타입이 들어오면 제약이 등식이 아니라 부등식이 되어 해가 여럿 생기고, 추론 결과가 복잡한 제약 집합으로 커집니다. 그래서 실용 언어는 지역 타입 추론이나 양방향 검사로 범위를 좁힙니다."
    },
    {
      "type": "text",
      "prompt": "두 타입 식을 같게 만드는 대입들 중, 다른 모든 해가 그것의 인스턴스로 얻어지는 가장 일반적인 대입을 무엇이라 하는가?",
      "answers": [
        "최일반 단일자",
        "최일반 단일화자",
        "가장 일반적인 단일자",
        "most general unifier",
        "mgu"
      ],
      "explanation": "최일반 단일자(most general unifier, mgu)입니다. 단일화 알고리즘은 해가 있을 때 반드시 mgu를 돌려줍니다."
    },
    {
      "type": "ox",
      "prompt": "알고리즘 W는 타입 환경과 식을 받아 대입과 타입을 돌려주며, HM 타입 시스템에 대해 건전하고 완전하다.",
      "answer": true,
      "explanation": "건전성은 돌려준 타입이 실제로 유도 가능함을, 완전성은 잘-타입된 식이면 반드시 주 타입을 찾아냄을 뜻합니다. 이것이 HM 추론의 고전적 결과입니다."
    },
    {
      "type": "choice",
      "prompt": "다음 중 표준 HM이 표기 없이 추론해낼 수 없는 것은?",
      "choices": [
        "let으로 묶인 항등 함수의 다형 타입",
        "다형 함수를 인자로 받아 여러 타입에 적용하는 랭크-2 함수",
        "리스트 길이를 세는 함수의 타입",
        "map 같은 고차 함수의 다형 타입"
      ],
      "answer": 2,
      "explanation": "인자 자리에 ∀가 들어가는 랭크-2 이상의 타입은 HM의 prenex 다형성 밖이라 표기가 필요합니다. 나머지는 모두 HM이 주 타입으로 추론합니다."
    },
    {
      "type": "ox",
      "prompt": "가변(mutable) 컨테이너 타입을 원소 타입에 대해 공변으로 두어도 타입 안전성은 그대로 유지된다.",
      "answer": false,
      "explanation": "읽기와 쓰기가 모두 가능한 위치는 불변(invariant)이어야 합니다. 공변으로 두면 좁은 타입 컨테이너에 넓은 타입 값을 써 넣는 코드가 통과해 버립니다."
    },
    {
      "type": "ox",
      "prompt": "불변(invariant) 위치는 공변도 반변도 아닌 위치로, 원소를 읽고 쓰는 일이 모두 가능한 가변 셀의 타입 인자가 대표적이다.",
      "answer": true,
      "explanation": "읽기만 하면 공변, 쓰기만 하면 반변으로 둘 수 있지만 둘 다 가능하면 어느 쪽으로도 완화할 수 없습니다. 언어에 따라 읽기 전용/쓰기 전용 뷰를 나눠 변성을 회복하기도 합니다."
    },
    {
      "type": "ox",
      "prompt": "점진적 타입 보장(gradual guarantee)에 따르면, 잘 돌던 프로그램에 타입 표기를 더 붙였을 때 결과 값 자체가 다른 값으로 바뀔 수 있다.",
      "answer": false,
      "explanation": "표기를 더 정밀하게 붙이면 같은 결과를 내거나 캐스트 오류로 실패할 뿐, 다른 값을 내지는 않습니다. 표기를 지우는 방향으로는 성공하던 동작이 그대로 유지됩니다."
    }
  ]
}
