본문으로 건너뛰기
피드

Elixir 1.20, 이제 점진적 타입 언어로 한 발 들어감

backend 약 7분
vote
0
댓글
북마크

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

  • 1

    Elixir 1.20은 set-theoretic type system 기반의 타입 추론과 점진적 타입 검사를 도입함

  • 2

    dynamic() 타입은 타입 정보를 버리는 any()와 달리 호환성 검사와 타입 좁히기를 수행함

  • 3

    If T 벤치마크 13개 범주 중 12개를 통과했고, 컴파일 시간 개선과 module_definition 옵션도 추가됨

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

  • Elixir 1.20이 “점진적 타입 언어”로 가는 첫 개발 마일스톤을 완료함

    • 2022년에 set-theoretic type system 도입을 발표했고, 2023년에는 타입 시스템 설계 논문을 공개했음
    • 이번 릴리스에서는 타입 애노테이션을 새로 요구하지 않고도 모든 Elixir 프로그램에 타입 추론과 점진적 타입 검사를 수행함
  • 목표는 기존 Elixir 코드에서 죽은 코드와 “검증된 버그”를 잡는 것임

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

중요

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

댓글

댓글

댓글을 불러오는 중...

backend

Gleam 1.17.0, 단일 파일 BEAM 실행 파일과 IDE 편의 기능을 잔뜩 추가했다

Gleam 1.17.0이 공개되면서 Erlang VM용 단일 파일 실행 배포 방식인 escript export를 공식 지원한다. 여기에 변수 참조 하이라이트, todo 상수 표현식, import 제안, 패턴 매칭 최적화, 여러 언어 서버 코드 액션, 보안 수정까지 포함됐다.

backend

인텔, 클라우드·통신사·에이전틱 AI용 제온6+ 정식 출시

인텔이 E코어 기반 서버 프로세서 제온6+를 정식 출시했다. 인텔 18A 공정, 리본펫, 파워비아, 포베로스 3D, EMIB까지 파운드리 기술을 총동원해 소켓당 최대 288코어를 밀어 넣은 제품이다. 인텔은 오래된 제온 서버를 제온6+로 바꾸면 랙과 서버 수를 크게 줄이고, 확보한 전력·공간을 AI 인프라로 돌릴 수 있다고 강조한다.

backend

내구성 있는 워크플로, 꼭 거창한 DB가 필요할까? SQLite로도 충분하다는 주장

Obelisk 글은 내구성 있는 실행에서 정말 중요한 건 비싼 인프라가 아니라 워크플로 상태를 오래 보존하는 것이라고 주장한다. 많은 AI 에이전트나 실험성 워크플로에서는 SQLite 파일과 Litestream 백업만으로도 충분하고, 고가용성 공유 DB가 필요한 시점에 Postgres로 가면 된다는 얘기다.

backend

DBOS 주장: Durable Workflow, 오케스트레이터 말고 Postgres로 충분하다

DBOS는 durable workflow를 구현할 때 Temporal, Airflow, AWS Step Functions 같은 외부 오케스트레이터가 꼭 필요하지 않다고 주장한다. 핵심 아이디어는 워크플로우 상태를 어차피 데이터베이스에 체크포인트로 저장한다면, Postgres 자체를 오케스트레이터처럼 쓰는 편이 더 단순하다는 것이다. 확장성, 가용성, 관측성, 보안까지 Postgres 운영 경험을 그대로 활용할 수 있다는 게 글의 논지다.

backend

Go에서 Rust로 옮길 때 진짜로 바뀌는 것들

이 글은 Go 백엔드 서비스를 Rust로 옮길 때 속도보다 컴파일 타임 보장, 런타임 트레이드오프, 개발자 경험이 더 중요하다고 설명한다. nil 패닉, 데이터 레이스, 에러 처리, 제네릭, 비동기 모델, 마이그레이션 전략까지 실무 관점에서 Go와 Rust를 길게 비교한다.