Gleam v1.19.0은 Erlang 소스 코드 대신 중간 표현인 Erlang abstract forms를 생성하며, Erlang 컴파일러의 토큰화와 파싱 단계를 건너뛰어 빌드 시간을 줄임
- 런타임의
소스 위치 메타데이터가 원본 Gleam 코드와 정확히 대응해, BEAM 오류 보고서와 스택 추적에서 정확한 줄 번호를 확인할 수 있음
BEAM 바이트코드 직접 생성은 채택하지 않음. 가상 머신의 변경을 따라가고 기존 최적화를 재구현하는 부담 때문에 중간 표현이 비용 대비 효과가 좋은 선택임
JavaScript 코드 생성은 패턴 매칭의 중첩 분기와 중간 변수를 줄이고, 짧은 리스트를 배열 변환 없이 직접 구성하도록 개선됨
TypeScript 타입 보존과 외부 빌드 도구 연동을 개선하고, 언어 서버의 레이블 탐색과 이름 변경, 브라우저 내 포맷팅, 오류 진단도 강화함
Erlang 소스 대신 중간 표현 생성
- Erlang 가상 머신과 JavaScript 런타임을 위한 타입 안전성과 확장성을 갖춘 언어 Gleam의 v1.19.0이 출시됨
- 새
Erlang 코드 생성기는 기존 설계를 전면 교체하고, 소스 코드 대신 Erlang abstract forms를 출력함
- Erlang abstract forms는 Erlang 구문을 나타내는 메타데이터가 붙은 트리이며, 일반적으로 Erlang의 토큰화와 파싱을 거쳐 생성되는 중간 표현임
- external term format으로 바이너리 인코딩해 직접 불러오므로 Erlang 컴파일러의 전반부를 건너뜀
빌드 성능과 원본 코드의 위치 정보가 개선됨
-
Erlang에서 실행하는 Gleam 프로젝트의 빌드 시간이 크게 줄어듦
-
기존 스택 추적은 가장 가까운 함수만 가리키는 등 부정확할 수 있었지만, 이제 BEAM 오류 보고서와 스택 추적의 줄 번호가 원본 Gleam 코드에 정확히 대응함
-
이 메타데이터로 edb 같은 디버거의 완전한 Gleam 지원이 가능해질 수 있으나, Gleam 팀이 직접 진행한 작업은 아직 없음
-
오래되고 안정적이지만 현재의 개발 기준과 관례에는 맞지 않던 생성기를 교체해
컴파일러 코드 품질도 높임
트랜스파일러(transpiler) 는 소스 코드처럼 사람이 읽을 수 있는 형식을 출력하는 컴파일러를 뜻하며, 그 자체로 열등하거나 구현하기 쉽다는 뜻은 아님
-
출력 형식을 보기 좋게 다듬어야 한다면 바이너리 출력보다 구현이 더 어려울 수도 있음
컴파일 벤치마크와 해석의 한계
- José Valim의
langcompilebench
를 바탕으로 한 벤치마크는 모듈 100개, 모듈마다 "hello world"
문자열을 반환하는 함수 100개를 컴파일하는 시간을 측정함
-
여러 언어에서 유사한 프로젝트를 쉽게 만들 수 있지만, 각 언어 기능의 작은 일부만 시험함
-
실제 프로젝트는 훨씬 다양한 코드를 사용하며, 기능별 컴파일 비용도 언어마다 다름
-
코드 생성기 재작성의 첫 단계는
v1.18.0에 반영됐으며, 이번 비교는 v1.17.0과 v1.19.0을 대상으로 상당한 빌드 시간 개선을 확인함
-
캐시 없이 처음부터 전체를 빌드하는 조건임
-
Gleam은 증분 컴파일을 사용하므로 일반적인 개발 과정에서는 전체 프로젝트를 다시 컴파일하지 않아 더 빠름
-
기존 Erlang, Elixir, Gleam 비교에 다른 대중적인 언어들과
Gleam의 JavaScript 타깃도 추가함
-
결과는 Gleam의 빠른 컴파일을 뒷받침하지만,
인위적인 벤치마크 하나만으로 언어 간 확정적인 결론을 내릴 수는 없음. 충분한 이해에는 추가 조사와 실제 사용 경험이 필요함
BEAM 바이트코드를 직접 생성하지 않는 이유
-
Erlang 컴파일러를 우회하면 더 빠른 바이트코드 생성이나
Gleam 타입 정보를 활용한 최적화가 가능할 수 있지만, 현재 자원으로 이를 달성할 가능성은 낮게 봄
BEAM 바이트코드는 Erlang 소스와 abstract forms와 달리 고정된 형식이 아님
-
가상 머신의 새 릴리스마다 기능이 추가되거나 불필요해진 기능이 제거될 수 있음
-
직접 생성하려면 이 변화를 계속 추적하고, Erlang 유지보수자와 긴밀히 협력하며, 새 가상 머신 출시에 맞춰 Gleam 버전을 준비해야 함
-
Gleam의 정적 분석을 활용하더라도, Erlang 컴파일러가
수십 년간 축적한 최적화를 재구현하려면 상당한 노력이 필요함
-
Gleam은 기업이나 학술기관이 뒷받침하는 언어보다 재원이 적은
후원 기반 커뮤니티 프로젝트임
-
수년에서 수십 년 동안 유지할 수 있는 선택이 필요하며, 현재는 abstract forms가 비용 대비 효과와 지속 가능성의 균형점임
-
Elixir도 Erlang abstract forms로 컴파일함
JavaScript 패턴 매칭과 리스트 최적화
패턴 매칭은 중첩 if
로 컴파일되며, 선언적인 패턴을 바탕으로 컴파일러가 분할 정복 방식으로 검사 순서를 재배치하고 최적화함
- 새 생성기는
중첩 조건문을 단일 조건으로 합치고 중간 변수를 줄임
Wibble(1, 2)
를 검사하는 예제는 여러 단계의 if
대신 isWibble(x) && x[0] === 1 && x[1] === 2
조건을 생성함
-
최소화와 gzip 압축을 거친 번들 크기는 거의 변하지 않지만, JavaScript 엔진이 최적화해야 할 분기는 줄어듦
-
Gleam의 리스트는 JavaScript의 변경 가능한 연속 배열과 다른
불변 영속 리스트이므로 별도의 런타임 자료구조를 구성해야 함
-
기존
[1, 2, 3]
리터럴은 JavaScript 배열을 만든 뒤 arrayToList([1, 2, 3])
로 변환했음
- 짧은 리스트는 이제
prepend(1, prepend(2, prepend(3, empty)))
처럼 직접 구성함
짧은 리스트 최적화는 현대적인 JavaScript 엔진에서 성능을 개선하며, Lustre처럼 짧은 리스트를 많이 사용하는 프로젝트에서 특히 효과가 큼
- 긴 리스트에서는 개선이 관측되지 않아 기존 배열 변환 방식을 유지함
- 코드 예제는 가독성을 위해 일부 편집됐지만, 최적화와 관련된 부분은 바꾸지 않음
TypeScript 연동에서 제네릭 타입 보존
- JavaScript 타깃은 사용자 정의 자료구조를 다루는 함수를 생성하며,
TypeScript 선언 파일도 제공해 한 프로젝트에서 두 언어를 통합할 수 있음
- 특정 변형인지 검사하는 함수에
오버로드를 추가해 가능한 경우 타입 매개변수를 보존함
Box(value)
에 Full(value)
와 Empty
가 있을 때, 기존 Box$isFull(value: any): value is Full<unknown>
선언은 이미 Box<number>
로 알려진 값도 Full<unknown>
으로 좁혀 타입 정보를 잃었음
- 새
Box$isFull<I>(value: Box$<I>): value is Full<I>
오버로드는 제네릭 타입을 유지하며, 기존 any
입력용 선언도 남아 있음
Mix와 rebar3 연동을 위한 빌드 명령 개선
- Gleam, Elixir, Erlang은 런타임에서
추가 비용 없는 상호운용이 가능하지만, Elixir의 Mix와 Erlang의 rebar3에는 Gleam 지원이 내장돼 있지 않아 빌드 연동이 어려울 수 있음
- 이번 릴리스는
외부 빌드 도구용 명령을 개선해 Mix와 rebar3의 Gleam 지원을 준비함
- BEAM 컴파일 시 가상 머신이 요구하는
.app
리소스 파일을 gleam
이 생성함. 이전에는 외부 빌드 도구가 이를 제공해야 했음
compile-package --no-dev
는 src
디렉터리의 코드만 읽고 dev_dependencies
에 나열된 패키지는 건너뜀
export package-information
과 export package-interface
는 파일뿐 아니라 표준 출력으로도 정보를 내보낼 수 있음
export javascript-prelude
와 export typescript-prelude
는 파일 출력이 가능해짐
Mix의 Gleam 지원은 아직 기대 단계이며, 이번 릴리스가 지원 완료를 뜻하지는 않음
언어 서버와 브라우저 포맷터
- 내장 언어 서버가 필드와 인자의
레이블에 대해 정의로 이동, 참조 찾기, 이름 변경을 지원함
- 컴파일러의
WebAssembly 빌드에 format_source
함수를 추가해 브라우저에서 Gleam 코드를 포맷팅할 수 있음
- 이 빌드는 언어 투어와 플레이그라운드의 브라우저 내 컴파일에 사용됨
- 플레이그라운드의 포맷팅 기능은 가까운 시일 내 추가할 예정임
오류 메시지와 불완전한 코드 처리
- 작은 문법 실수에 대한
전용 오류 메시지를 추가함
- 코드에 Git 병합 충돌 마커가 남아 있는 경우를 구별함
- 올바른
User(..lucy, score: 10)
대신 User(score: 10, ..lucy)
처럼 원본 레코드를 잘못된 위치에 놓은 레코드 갱신을 진단함
, *=
연산자와, Java 등에서는 유효하지만 Gleam에서는 잘못된 패턴 매칭의 |
사용을 진단함
-
상수 표현식에서 허용되지 않는
이항 연산자에 대한 메시지와 오류가 있는 코드의 처리 능력을 개선함
-
컴파일러는 언어 서버의 핵심이므로, 리팩터링이나 큰 편집 도중 코드가 유효하지 않더라도 정보를 제공해야 함
-
같은 패키지의 다른 모듈에서
비공개 타입이나 값을 사용하면, 해당 항목이 존재하지만 비공개라는 맥락을 알려줌
-
개발자가 유지보수하지 않는 코드의 정보를 노출하지 않도록 의존성 패키지의 모듈에는 적용하지 않음
-
잘못된
타입 별칭 정의가 별칭을 사용하는 모든 위치에서 연쇄 오류를 일으키지 않도록 타입 검사기를 개선함
-
그 밖의 수정과 개선 내역은 전체 변경 기록에서 확인할 수 있음
후원 기반 개발의 지속 가능성
- Gleam은 기업 소유가 아니며
전적으로 후원으로 운영되고, 후원자 대부분은 매달 5~20달러를 기여함
- 핵심 팀원에게 적절한 보수를 지급하는 목표에 가까워졌지만 아직 추가 재원이 필요해, 프로젝트와 핵심 팀원 후원을 요청함
댓글과 토론