Skip to content
Published on

Elixir 1.20의 점진 타입 시스템 — 어노테이션 없이 "검증된 버그"를 찾는다는 것의 실제

공유하기
Authors

들어가며 — 4년 만에 끝난 첫 마일스톤

Elixir 팀이 집합론적 타입(set-theoretic types)을 컴파일러에 넣겠다고 발표한 건 2022년입니다. 2023년에는 Giuseppe Castagna, Guillaume Duboc, José Valim이 The Design Principles of the Elixir Type System으로 설계 원리를 발표하면서 "연구에서 개발로 넘어간다"고 선언했습니다. 그리고 2026년 6월 3일, Elixir v1.20이 나오면서 그 첫 번째 마일스톤이 끝났습니다.

마일스톤의 내용은 이렇게 요약됩니다 — 타입 어노테이션을 하나도 도입하지 않은 채로, 모든 Elixir 프로그램에 대해 타입 추론과 점진적 타입 체크를 수행한다. 당신이 코드를 한 줄도 고치지 않아도 컴파일러가 죽은 코드와 "검증된 버그(verified bugs)"를 찾아 준다는 뜻입니다. 여기서 검증된 버그란 발표문의 정의대로 실행되면 런타임에 반드시 실패하도록 보장된 타이핑 위반을 말합니다.

이 글은 하이프를 걷어내고 세 가지를 봅니다. (1) 1.20이 실제로 새로 하는 일, (2) 발표문의 수치를 원본 소스에 대조하면 무엇이 남는지, (3) 문서에 적혀 있지만 발표문에는 없는 트레이드오프. Elixir 1.18과 OTP 27/28 시절의 전반적인 생태계 지형은 모던 Elixir/Phoenix 2026 편과 모던 Erlang/BEAM 2026 편에서 다뤘으니, 여기서는 타입 시스템만 파고듭니다.

dynamic()이 any()와 다른 지점

Elixir 타입 시스템의 목표는 공식 문서 기준으로 세 가지입니다 — 건전성(sound): 추론·할당된 타입이 프로그램의 실제 동작과 일치한다. 점진성(gradual): dynamic() 타입이 있고, dynamic()이 없으면 정적 타입 시스템처럼 동작한다. 개발자 친화성: 타입을 합집합·교집합·부정이라는 기본 집합 연산으로 기술한다(그래서 "집합론적"입니다).

여기서 핵심은 dynamic()입니다. 많은 점진 타입 시스템의 any()는 타입 시스템 관점에서 "아무거나 다 된다"를 뜻하고, 그래서 위반을 아예 보고하지 않습니다. Elixir의 dynamic()은 다릅니다. 발표문의 표현으로는 두 가지 성질을 갖습니다 — 호환성(compatibility)좁히기(narrowing).

호환성부터 봅시다. 발표문이 든 예를 그대로 옮기면 이렇습니다.

def percentage_or_error(value) when is_integer(value) do
  value_or_error =
    if value > 1 do
      value
    else
      "not well"
    end
  # ... more code ...
  if value > 1 do
    value_or_error / 100
  else
    String.upcase(value_or_error)
  end
end

value_or_error는 정수이거나 바이너리입니다. / 연산자는 숫자만, String.upcase는 바이너리만 받습니다. 엄격한 정적 타입 시스템이라면 위반 두 개를 보고할 겁니다. 그런데 이 프로그램은 런타임에 아무 예외도 내지 않습니다. 타입 시스템이 프로그램의 의도를 정밀하게 포착하지 못해 생기는 거짓 양성(false positive)입니다.

Elixir는 이 변수에 dynamic(integer() or binary()) 타입을 붙입니다. 그리고 dynamic() 타입 값을 함수에 넘길 때는 공급된 타입과 허용된 타입이 서로소(disjoint)일 때만 위반을 냅니다. /가 숫자만 받더라도 dynamic(integer() or binary())는 정수일 수 있으니 서로소가 아니고, 따라서 위반이 아닙니다. 반면 이렇게 바꾸면,

value_or_error =
  if value > 1 do
    value
  else
    "not well"
  end

Map.fetch!(value_or_error, :some_key)

Map.fetch!는 맵을 기대하는데 이 값은 런타임에 정수 아니면 바이너리뿐입니다. 서로소이므로 위반입니다. 이것이 Elixir가 "검증된 버그만" 보고한다고 말하는 근거입니다.

그런데 검증된 버그만 보고하면서 버그를 별로 못 찾으면 쓸모가 없겠죠. 그래서 좁히기가 붙습니다.

def add_a_and_b(data) do
  data.a + data.b
end

datadynamic()으로 시작하지만 data.a, data.b가 덧셈에 쓰이는 걸 보고 %{..., a: number(), b: number()}로 좁혀집니다(앞의 ...은 다른 키가 있어도 된다는 뜻). 그래서 .b를 빠뜨리고 data.a + data라고 쓰면 data가 먼저 맵으로 좁혀졌다가 숫자로 쓰이려 하니 위반이 납니다.

한 줄로 말하면 — Elixir의 dynamic()은 범위처럼 동작하고, 코드를 따라가며 좁혀지며, 타입 체크가 그 범위를 벗어날 때 위반을 냅니다. 다른 점진 타입 시스템이 dynamic 타입으로 타입 정보를 버리는 것과 정반대 방향입니다.

1.20이 실제로 새로 하는 일

CHANGELOG 기준으로 1.20에 새로 들어온 것들을 봅시다.

가드 추론. 이번 릴리스의 마지막 퍼즐 조각이었습니다.

def example(x, y) when is_list(x) and is_integer(y)
# x는 리스트, y는 정수

def example({:ok, x} = y) when is_binary(x) or is_integer(x)
# x는 바이너리 또는 정수, y는 :ok로 시작하는 2원소 튜플

def example(x) when is_map_key(x, :foo)
# x는 :foo 키를 가진 맵 -> %{..., foo: dynamic()}

def example(x) when not is_map_key(x, :foo)
# x는 :foo 키가 없는 맵 -> %{..., foo: not_set()}
# 따라서 함수 본문의 x.foo는 타이핑 위반

def example(x) when tuple_size(x) < 3
# 튜플 원소가 최대 2개 -> elem(x, 3)은 위반

부정(not is_map_key)이 not_set()이라는 타입으로 표현되는 게 집합론적 타입 시스템다운 지점입니다. 맵·리스트의 크기 검사는 "비어 있는지" 검사로 변환됩니다.

함수 본문 전체 추론. 1.18까지는 패턴에서만 추론했습니다. 이제 본문을 봅니다.

def sum_to_string(a, b) do
  Integer.to_string(a + b)
end

+는 정수와 부동소수 둘 다 되지만, 결과가 정수를 요구하는 함수로 들어가므로 ab가 둘 다 정수라고 추론합니다. 역방향으로 정보가 흐르는 셈입니다.

절 사이 추론과 occurrence typing. case, cond, with에 occurrence typing이 붙었고, 앞선 절이 뒷 절을 좁힙니다.

case System.get_env("SOME_VAR") do
  nil -> :not_found
  value -> {:ok, String.upcase(value)}
end

System.get_env/1은 nil 아니면 바이너리를 반환하는데, 첫 절이 nil을 잡았으니 value는 바이너리일 수밖에 없고, 그래서 String.upcase가 위반 없이 통과합니다. 덤으로 중복 절(redundant clauses) 경고가 생겼습니다.

의존성을 가로지르는 추론. CHANGELOG의 "Perform type inference across applications" 항목입니다. 1.20-rc 이전에는 표준 라이브러리 함수 호출에서만 타입을 추론했고, 당신의 의존성 모듈을 호출하면 타입 체크만 하고 추론은 하지 않았습니다. 이제 의존성에서 추론한 타입 정보가 당신 앱의 추론에 흘러들어옵니다. 컴파일러를 통과하는 타입 정보량이 크게 늘어나는 변화라, Elixir 팀은 2026년 1월 로드맵 글에서 이 단계에만 릴리스 후보 하나를 통째로 배정했다고 밝혔습니다.

실제로 1.20은 릴리스 후보를 일곱 개(rc.0부터 rc.6까지) 거쳤습니다. rc.0이 2026년 1월 9일, rc.6이 5월 21일, 최종 릴리스가 6월 3일입니다. 1월 로드맵 글이 예고한 최종 릴리스 시점은 "5월"이었으니 한 달쯤 밀린 셈인데, 타입 시스템 전면 도입치고는 준수한 편입니다.

"13개 중 12개 통과"를 원본에 대조해 봤다

발표문에서 가장 눈에 띄는 수치는 이것입니다.

our implementation performs well in the "If T: Benchmark for Type Narrowing" benchmark. Elixir passes 12 of the 13 categories

If-T는 실존하는 제3자 벤치마크입니다. Hanwen Guo와 Ben Greenman이 만들었고(arXiv:2508.03830), 유타대 PLT 그룹의 utahplt/ifT-benchmark 저장소에 있습니다. 타입 좁히기(occurrence typing)를 재는 벤치마크이고, 주제마다 "타입 체크를 통과해야 하는 프로그램"과 "통과하면 안 되는 프로그램"을 짝으로 둡니다. 카테고리는 정확히 13개입니다 — positive, negative, connectives, nesting_body, struct_fields, tuple_elements, tuple_length, alias, nesting_condition, merge_with_union, predicate_2way, predicate_1way, predicate_checked.

그런데 발표문이 이 문장에 걸어 둔 링크는 저장소의 Benchmark Results 표입니다. 그 표를 열어 보면 — Elixir 열이 없습니다.

오늘(2026-07-16) 기준으로 확인한 내용은 이렇습니다. 상류 결과표가 다루는 타입 체커는 Typed Racket, TypeScript, Flow, mypy, Pyright, Sorbet, Luau, MLsem, Typed Clojure, ty, Pyrefly의 11개입니다. 저장소 트리(106개 항목)의 언어 디렉터리도 이 11개뿐이고 Elixir 디렉터리는 없습니다. README 전문에서 대소문자 무시하고 "elixir"를 찾으면 0건이며, Elixir 추가를 제안한 PR도 열린 것·닫힌 것 통틀어 없습니다.

그러니 정확히 말하면 이렇습니다 — "13개 중 12개 통과"는 Elixir 팀의 자체 측정치이고, 발표문이 링크한 결과표는 그 수치를 뒷받침하지 않습니다. 틀렸다는 뜻이 아닙니다. 상류에 재현 가능한 구현물이 올라가 있지 않아 제3자가 확인할 수 없다는 뜻입니다. 어느 카테고리 하나를 통과하지 못했는지도 공개된 바 없습니다(검색 결과 요약에는 특정 카테고리를 지목하는 문장이 떠다니지만, 어떤 1차 소스에서도 확인되지 않아 여기서는 인용하지 않습니다).

맥락을 위해 상류 표의 실제 숫자를 옮겨 두면, 자체 측정치의 위치를 가늠하는 데 도움이 됩니다.

상류 If-T 결과표(utahplt/ifT-benchmark, v1.1) — 13개 카테고리 기준
  Typed Clojure : 13/13  (전 항목 통과)
  Typed Racket  : 12/13  (tuple_length 실패)
  MLsem         : 12/13  (connectives 실패)
  Flow          : 11/13
  Pyright       : 11/13
  TypeScript    : 10/13  (nesting_condition, predicate_1way, predicate_checked 실패)
  mypy          :  9/13

  Elixir        : 표에 없음 (발표문 자체 측정 12/13)

12/13이 사실이라면 Typed Racket과 동급이고 TypeScript보다 앞선다는 의미가 됩니다. 어노테이션이 전혀 없는 코드에서 그 정도 좁히기 정밀도가 나온다면 실제로 인상적인 결과입니다. 다만 지금 상태로는 저자 자체 측정이고, 조건과 실패 항목이 공개되지 않았으며, 링크된 표로는 대조할 수 없다는 꼬리표를 붙여야 정확합니다.

컴파일 시간 — "가장 빠르다"는 주장의 실제 크기

발표문의 또 하나의 주장은 이겁니다 — "우리 합성 벤치마크에서 이제 Elixir의 빌드 도구가 BEAM 언어 중 가장 빠르다."

근거는 José Valim 본인의 저장소 josevalim/langcompilebench입니다. 저자 자체 측정이고, 저장소 스스로 "합성 벤치마크(synthetic benchmarks)"라고 밝힙니다. 측정 대상은 100개의 독립 모듈, 각각 hello world 함수 100개를 컴파일하고, 애플리케이션을 부팅하고, 최소 테스트 스위트를 돌리는 시간입니다. 조건은 MacStudio M1, 5회 평균입니다.

Erlang/OTP 28 기준
  Elixir v1.19                        ~0.73s
  Elixir v1.20                        ~0.63s
  Elixir v1.20 (interpreted defmodule) ~0.58s
  Erlang (rebar3)                     ~0.72s
  Gleam v1.14                         ~0.71s

Erlang/OTP 29 기준
  Elixir v1.19                            ~0.65s
  Elixir v1.20-rc.2                       ~0.55s
  Elixir v1.20-rc.2 (interpreted defmodule) ~0.50s
  Erlang (rebar3)                         ~0.64s
  Gleam v1.14                             ~0.67s

숫자를 그대로 읽으면 — 1.19에서 1.20으로 오면서 0.73초가 0.63초가 됐고, Erlang의 0.72초와 Gleam의 0.71초를 앞섭니다. 사실입니다. 동시에 전체 작업이 0.7초 남짓이고 격차가 0.1초 안팎이라는 것도 사실입니다. 그리고 이 워크로드는 hello world 함수 덩어리이지 실제 코드가 아닙니다 — 타입 체크를 무겁게 돌릴 만한 코드가 애초에 없습니다.

저자 본인이 저장소에 달아 둔 단서를 그대로 옮기는 게 가장 정직합니다.

물론 이것이 같은 규모의 Elixir 프로젝트가 Gleam이나 Erlang 프로젝트보다 빠르게 컴파일된다는 뜻은 아닙니다. 원시 컴파일러 성능 외에도 변수가 더 있기 때문입니다. (…) 이론적으로 말하면 rebar3가 이 중 가장 좋은 성능을 내야 합니다.

증분 컴파일로 넘어가면 순위가 아예 뒤집힙니다. 같은 저장소의 분석에 따르면 모듈 간 의존성을 컴파일타임/런타임으로 구분하는 능력에서 갈리는데, Erlang은 (드물게 쓰이는 parse transform을 빼면) 런타임 의존성만 있어서 여기 등장하는 언어 중 증분 성능이 가장 좋습니다. 파일 하나를 바꾸면 그 파일만 다시 컴파일됩니다. Gleam은 모든 의존성을 컴파일타임으로 취급해서 가장 나쁩니다. Elixir는 그 중간입니다.

langcompilebench의 정리표
  언어     | 의존성 종류            | 순환 허용 | 변경당 예상 재컴파일
  Erlang   | 런타임만               | 예        | 최저
  Elixir   | 런타임 + 컴파일타임    | 예        | 중간
  Gleam    | 컴파일타임만           | 아니오    | 최고

즉 "가장 빠르다"는 주장은 클린 빌드 합성 벤치마크에 한정된 이야기이고, 개발 중에 실제로 체감하는 증분 빌드에서는 Erlang이 앞선다는 게 같은 저장소의 결론입니다. 발표문만 읽으면 놓치는 맥락입니다.

공짜가 아닌 부분

문서에는 적혀 있는데 발표문에는 없는 트레이드오프들입니다.

dynamic은 항상 루트에 있다. 공식 문서의 설명대로, {:ok, dynamic()} 같은 튜플을 쓰면 Elixir는 이를 dynamic({:ok, term()})으로 다시 씁니다. 문서가 명시한 단점은 이것입니다 — 튜플·맵·리스트의 일부만 gradual하게 만들 수 없고 전체가 gradual해집니다. 대신 dynamic이 항상 루트에 명시적으로 드러나서, 정적으로 타이핑된 프로그램에 dynamic이 몰래 스며드는 걸 막아 준다는 게 장점으로 제시됩니다. 의도된 설계 결정이지만 정밀도를 내주는 거래인 건 분명합니다.

구조체 업데이트는 설계상 위반을 낸다. 문서가 직접 든 예입니다.

user = find_user_by_id(42)
%User{user | name: "John Doe"}

find_user_by_id가 런타임에 항상 User 구조체를 반환하더라도, 타입 시스템이 그걸 정적으로 증명하지 못하면 타이핑 위반을 냅니다. 문서의 표현으로는 "구조체 업데이트가 설계상 동작하는 방식"입니다. 해결책은 변수를 정의할 때 매칭해 주는 것입니다.

%User{} = user = find_user_by_id(42)
%User{user | name: "John Doe"}

"거짓 양성률이 극히 낮다"는 발표문의 주장은 수치 없이 제시된 벤더 자체 평가이고, 이런 식으로 코드를 손봐야 하는 자리가 남아 있다는 점은 같이 봐야 합니다.

interpreted defmodule은 대가가 있다. 위 벤치마크에서 가장 빠른 숫자를 낸 module_definition: :interpreted 옵션은 발표문에서 장점만 언급되지만, CHANGELOG에는 단점이 적혀 있습니다.

  • 컴파일 중 발생하는 에러의 스택트레이스가 덜 정밀해질 수 있다
  • defmodule 안의 익명 함수가 인자를 최대 20개까지만 받을 수 있다(def 등으로 정의한 함수 자체는 여전히 255개까지 가능)

기본값은 :compiled이고, 켜려면 mix.exselixirc_options: [module_definition: :interpreted]를 넣습니다. .beam 파일 자체는 달라지지 않습니다 — defmodule 내부를 실행하는 방식만 바뀝니다.

업그레이드 전에 알아야 할 것

OTP 요구사항. 1.20은 Erlang/OTP 27 이상을 요구하고 OTP 29와 호환됩니다. OTP 26 이하에 묶여 있다면 1.20은 아예 선택지가 아닙니다.

보안 패치가 1.20에만 있다. 1.20.1(2026년 6월 9일)에 CVE-2026-49762(GHSA-w2h8-8x3g-278p, 심각도 medium, Erlang Ecosystem Foundation 발행)가 수정됐습니다. 내용은 Version 모듈의 무제한 정수 파싱입니다. 버전 문자열의 숫자 구성 요소를 길이 제한 없이 정수로 변환하다 보니, 전부 숫자인 큰 구성 요소 하나로 초선형·비양보(non-yielding) 변환이 일어나 BEAM 스케줄러 하나를 붙잡아 두고, 더 크면 SystemLimitError로 프로세스가 죽습니다. OSV 기록에 따르면 1메가바이트 정도의 문자열 하나면 충분하고 인증도 필요 없습니다. 도달 경로는 Version.parse/1, Version.parse!/1, Version.match?/3, Version.compare/2, Version.parse_requirement/1이고, HTTP 파라미터나 패키지 메타데이터처럼 신뢰할 수 없는 입력에 이 함수들을 호출하는 앱이 대상입니다. 수정은 정수 구성 요소를 10진 14바이트로 제한하는 것입니다.

여기서 중요한 대목 — 영향 범위는 1.5.0부터 1.20.1 미만 전체입니다. 그런데 확인해 보면 2026년 6월 9일 이후 나온 릴리스는 1.20.1과 1.20.2뿐이고, v1.19.6이나 v1.18.5 태그는 존재하지 않습니다. 즉 이 취약점의 패치를 받으려면 1.20.1 이상으로 올라가는 것 외에 방법이 없습니다. 1.19 이하에 머물 계획이라면, Elixir 팀 권고대로 Version 모듈에 넘기는 데이터 크기를 직접 제한해야 합니다.

하드 디프리케이션. 1.20에서 실제로 경고를 내기 시작하는 것들입니다.

  • File.stream!(path, modes, lines_or_bytes)File.stream!(path, lines_or_bytes, modes)로 인자 순서 변경
  • 비트 패턴 안에서 크기를 매칭할 때 핀 연산자 필요: <<x::size(^existing_var)>>
  • Kernel.ParallelCompiler.async/1Kernel.ParallelCompiler.pmap/2
  • Logger.*_backend 함수들 → 핸들러 방식으로 (계속 백엔드를 쓰려면 :logger_backends 패키지)
  • Logger.enable/1, Logger.disable/1Logger.put_process_level/2, Logger.delete_process_level/1
  • mix.exsxref: [exclude: ...]elixirc_options: [no_warn_undefined: ...]

잠재적 파괴 변경 두 가지. 문자열·주석과 ? 뒤의 raw CR 줄바꿈이 보안상 금지됐고, require SomeModule이 더 이상 컴파일타임에 해당 모듈로 확장되지 않습니다(런타임에 모듈을 반환하는 건 동일). CHANGELOG가 굳이 후자를 적어 둔 이유는 require(SomeMod).some_macro() 같은 코드가 깨질 수 있어서입니다.

타입 시그니처는 왜 아직 멀었나

가장 궁금한 질문 — 언제 @spec 대신 컴파일러가 강제하는 타입 시그니처를 쓸 수 있나?

발표문은 조건을 네 개 걸었습니다. 타입 시그니처는 다음이 충족될 때만 도입합니다.

  1. 1.20의 타입 시스템 성능에 만족할 때
  2. 재귀 타입(recursive types)을 효율적으로 구현할 수 있을 때
  3. 파라메트릭 타입(parametric types)을 효율적으로 구현할 수 있을 때
  4. 맵의 키-값 쌍 순회를 enumerable로 효율적으로 구현할 수 있을 때 — 여기는 아직 해법을 연구 중이라고 명시합니다

즉 남은 건 엔지니어링만이 아니라 미해결 연구 문제입니다. 1월 로드맵 글은 이 문제들을 v1.21(2026년 11월)과 v1.22(2027년 5월)에서 탐색하겠다고 밝히면서, 타입 시스템이 실용적이지 않은 것으로 판명될 수 있는 위험 두 가지를 스스로 적어 뒀습니다 — 에르고노믹스(지금까지의 개선은 전부 언어 변경 없이 무대 뒤에서 일어났고, 개발자 경험에 미칠 영향은 아직 평가되지 않았다)와 성능(현재 구현은 재귀·파라메트릭 타입을 지원하지 않으며 이들이 성능을 직접 타격할 수 있다).

남은 마일스톤 순서는 이렇습니다. 두 번째는 타입 있는 구조체(typed structs) — 구조체 필드의 타입을 네이티브로 정의해 코드베이스 전반에 전파시키는 것. 세 번째가 집합론적 타입 시그니처입니다.

그리고 여기 조용히 큰 뉴스가 하나 있습니다. 공식 문서가 명시한 내용입니다 — 기존 Erlang Typespec은 집합론적 타입에 쓰기엔 충분히 정밀하지 않아서, 이 단계가 끝나면 언어에서 단계적으로 제거되고(phased out) 후처리는 별도 라이브러리로 옮겨집니다. @spec을 열심히 써 온 코드베이스라면 언젠가 마이그레이션이 온다는 뜻입니다. 시점은 정해지지 않았습니다.

그래서 지금 무엇을 해야 하나

올려도 되는 경우. OTP 27+에 있고, 위의 디프리케이션 목록이 감당 가능하다면 올릴 이유는 충분합니다. 타입 체크는 옵트인이 아니라 그냥 켜져 있고, 코드를 고치지 않아도 됩니다. 얻는 게 공짜에 가깝습니다. 게다가 Version CVE 패치가 1.20.1 이상에만 있으므로, Version 모듈에 외부 입력을 넘기는 앱이라면 업그레이드가 사실상 보안 사안입니다.

서두를 필요 없는 경우. 타입 시그니처를 쓰고 싶어서 기다린 거라면 1.20은 그게 아닙니다. 최소 v1.21(2026년 11월), 현실적으로는 그 이후이고, 연구 문제가 안 풀리면 더 밀릴 수 있습니다. 구조체 타입도 아직입니다. OTP 26 이하에 묶여 있다면 애초에 못 올립니다.

기대치 조정. 1.20이 찾아 주는 건 "실행되면 반드시 터지는" 위반과 죽은 코드입니다. 타입이 맞는지 증명해 주는 게 아닙니다. 어노테이션이 없으니 컴파일러가 아는 건 코드에서 추론할 수 있는 것뿐이고, 애매한 곳은 dynamic()으로 남아 서로소일 때만 걸립니다. Dialyzer를 써 봤다면 익숙한 위치인데, 차이는 Elixir 1.20이 컴파일러에 내장돼 있고 별도 실행이나 PLT 빌드가 없다는 점입니다.

업그레이드 후 경고가 쏟아진다면 대부분 진짜 버그일 가능성이 높습니다. 발표문의 마지막 문장이 그 뜻입니다 — "Elixir v1.20을 써 보고, 공짜로 찾아 주는 버그들을 다 고치는 걸 잊지 마세요."

마치며

정리하면 이렇습니다. Elixir 1.20은 2022년에 시작한 타입 시스템 작업의 첫 마일스톤을 실제로 끝냈고, 이건 진짜입니다 — 어노테이션 없이 모든 프로그램을 점진적으로 타입 체크하고, dynamic()이 타입 정보를 버리는 대신 범위로 좁혀지며, 가드·함수 본문·절·의존성을 가로질러 추론이 흐릅니다. 비용은 사실상 업그레이드뿐입니다.

동시에, 발표문의 두 숫자는 원본에 대조하면 크기가 달라집니다. If-T "13개 중 12개"는 링크된 상류 표에 Elixir 열조차 없는 자체 측정치이고, 컴파일 속도 1위는 저자 본인의 hello-world 합성 벤치마크에서 0.7초 작업의 0.1초 차이이며 증분 빌드에서는 같은 저장소가 Erlang을 앞세웁니다. 두 주장 모두 거짓이 아니지만, 발표문만 읽고 얻는 인상보다는 작습니다.

그리고 정말 갖고 싶은 것 — 타입 시그니처와 타입 있는 구조체 — 은 아직 1년 넘게 남았고, Elixir 팀 스스로 "실용적이지 않은 것으로 판명될 수 있다"고 적어 둔 미해결 연구 문제 뒤에 있습니다. 그 정직함이 이 프로젝트가 4년째 신뢰를 유지하는 이유이기도 합니다.

참고 자료