---
title: "Elixir 1.20, 이제 점진적 타입 언어로 한 발 들어감"
published: 2026-06-03T19:02:26.000Z
canonical: https://jeff.news/article/3642
---
# Elixir 1.20, 이제 점진적 타입 언어로 한 발 들어감

Elixir 1.20은 타입 애노테이션 없이 모든 Elixir 프로그램에 타입 추론과 점진적 타입 검사를 적용하는 첫 개발 마일스톤을 담았음. 목표는 기존 동적 코드베이스에서 거짓 양성을 낮게 유지하면서, 실제 런타임에서 터질 버그와 죽은 코드를 잡아내는 것임.

## 타입 애노테이션 없이 시작하는 Elixir 타입 검사

- Elixir 1.20이 “점진적 타입 언어”로 가는 첫 개발 마일스톤을 완료함
  - 2022년에 set-theoretic type system 도입을 발표했고, 2023년에는 타입 시스템 설계 논문을 공개했음
  - 이번 릴리스에서는 타입 애노테이션을 새로 요구하지 않고도 모든 Elixir 프로그램에 타입 추론과 점진적 타입 검사를 수행함

- 목표는 기존 Elixir 코드에서 죽은 코드와 “검증된 버그”를 잡는 것임
  - 여기서 검증된 버그는 실행되면 런타임에서 반드시 실패하는 타입 위반을 뜻함
  - Elixir 팀은 개발자 부담 없이, 거짓 양성(false positive)을 극도로 낮게 유지하는 쪽을 우선순위로 잡았음

> [!IMPORTANT]
> Elixir 1.20은 If T: Benchmark for Type Narrowing에서 13개 범주 중 12개를 통과했다고 밝힘. 동적 코드에서 평범한 조건문과 가드만 보고도 꽤 정밀한 타입 정보를 회수한다는 얘기임.

## dynamic()이 any()랑 다른 이유

- Elixir의 dynamic()은 “아무거나 됨”이 아니라 “런타임에 확인될 수 있는 가능 범위”에 가까움
  - 많은 점진적 타입 시스템에서 any()는 타입 검사를 사실상 우회하는 통로처럼 동작함
  - Elixir의 dynamic()은 호환성(compatibility)과 좁히기(narrowing)라는 두 속성을 갖고 있음

- 호환성 규칙 덕분에 Elixir는 확실히 틀린 경우만 타입 위반으로 보고함
  - 예를 들어 값이 integer() 또는 binary()일 수 있고, 어떤 연산자가 number()만 받는다면 완전히 불가능한 건 아님. integer()일 수 있으니까 위반으로 보지 않음
  - 반대로 Map.fetch!처럼 map을 기대하는 함수에 integer() 또는 binary()만 가능한 값을 넘기면 타입 집합이 겹치지 않으므로 위반으로 잡음

- dynamic()은 코드가 값을 어떻게 쓰는지 보면서 점점 좁혀짐
  - 어떤 data에 대해 data.a와 data.b를 더하는 코드가 있으면, Elixir는 data를 a와 b 필드가 number()인 map으로 추론함
  - 그런데 data.a만 꺼낸 뒤 data 자체를 number()처럼 쓰면, 앞서 좁혀진 타입과 충돌하므로 위반을 보고함

## 가드, 분기, 표준 라이브러리까지 타입 정보가 흐름

- Elixir 1.20은 guard에서 합집합, 교집합, 부정 정보를 추론함
  - 리스트인지, 정수인지, binary 또는 integer인지 같은 정보를 함수 본문으로 넘길 수 있음
  - map에 특정 키가 있거나 없다는 정보도 표현함. 키가 없다고 추론된 map에서 x.foo를 접근하면 타입 위반이 나옴

- 자료구조 크기 검사도 타입 추론에 반영됨
  - 튜플이 최대 2개 원소를 가진다고 확인된 뒤 elem(x, 3)을 접근하면 위반으로 잡음
  - map과 list의 크기 검사는 비어 있는지 여부 같은 정보로 바꿔 추론에 사용함

- case와 조건문에서는 앞선 절의 정보를 다음 절에 반영함
  - System.get_env("SOME_VAR")가 nil 또는 binary()를 반환할 때, 첫 절에서 nil을 처리하면 다음 절에서는 binary()만 남는 식임
  - 이런 흐름 분석 덕분에 중복 절과 죽은 코드도 찾을 수 있음

## 컴파일 시간 개선과 다음 단계

- Elixir 1.20은 컴파일 시간도 다시 개선했다고 함
  - 특히 코어가 많은 머신에서 효과가 있다고 설명함
  - Elixir 빌드 도구가 BEAM 언어군의 합성 벤치마크에서 가장 빠른 축에 들어갔다고 밝힘

- 새 컴파일러 옵션 :module_definition도 추가됨
  - 기본값은 :compiled이고, :interpreted로 바꾸면 큰 프로젝트에서 컴파일 시간을 줄일 수 있음
  - mix.exs에 elixirc_options: [module_definition: :interpreted]로 설정 가능함
  - 디스크에 기록되는 .beam 파일에는 영향을 주지 않고, defmodule 내부 실행 방식에만 영향을 줌

- 타입 시그니처 도입은 아직 다음 단계임
  - Elixir 팀은 성능, 재귀 타입, 파라메트릭 타입, map key-value 순회 처리 같은 연구와 구현 문제가 남아 있다고 설명함
  - 이 문제가 해결된 뒤 typed struct 정의와 타입 시그니처 논의를 시작하겠다는 계획임

---
## 기술 맥락
- Elixir가 이번에 택한 방식은 타입 애노테이션을 먼저 요구하지 않는 거예요. 이미 돌아가는 동적 코드베이스에 갑자기 타입 선언을 요구하면 팀들이 바로 피로감을 느끼거든요. 그래서 컴파일러가 먼저 추론하고, 확실히 터질 버그만 알려주는 쪽으로 출발했어요.

- dynamic()이 중요한 이유는 기존 동적 코드의 애매함을 완전히 포기하지 않기 때문이에요. any()처럼 “검사 안 함”으로 빠지면 버그를 못 잡고, 너무 엄격하면 정상 코드까지 깨뜨려요. Elixir는 가능한 타입 범위를 들고 있다가 실제 사용 맥락으로 좁혀요.

- set-theoretic type system은 Elixir 문법과 잘 맞는 선택이에요. 패턴 매칭, guard, case 절에서 값의 가능성을 계속 나누고 빼는 일이 많기 때문에 합집합, 교집합, 부정 타입이 자연스럽게 쓰이거든요.

- 아직 타입 시그니처를 넣지 않은 것도 꽤 현실적인 판단이에요. 재귀 타입과 파라메트릭 타입을 효율적으로 처리하지 못하면 실전 라이브러리에서 바로 병목이 생겨요. 그래서 이번 릴리스는 “타입 시스템을 믿을 수 있나”를 먼저 검증하는 단계로 보는 게 맞아요.

## 핵심 포인트

- Elixir 1.20은 set-theoretic type system 기반의 타입 추론과 점진적 타입 검사를 도입함
- dynamic() 타입은 타입 정보를 버리는 any()와 달리 호환성 검사와 타입 좁히기를 수행함
- If T 벤치마크 13개 범주 중 12개를 통과했고, 컴파일 시간 개선과 module_definition 옵션도 추가됨

## 인사이트

Elixir가 타입 시스템을 붙이는 방식은 “정적 타입으로 갈아타자”가 아니라 “기존 동적 코드에서 믿을 만한 버그만 잡자”에 가깝다. 레거시 동적 언어가 타입을 도입할 때 가장 무서운 게 거짓 양성인데, Elixir는 그 신뢰 문제를 정면으로 잡고 있음.
