요약
Gleam 1.19는 Erlang 소스 대신 Erlang 추상 형식을 바로 만들어 빌드가 빨라졌습니다.
Gleam 팀이 v1.19.0 출시 소식에서 밝힌 변화입니다. Gleam은 그동안 Erlang 소스 코드를 만들어 Erlang 컴파일러에 넘겼는데, 자코모 카발리에리가 이 코드 생성기를 새로 짰습니다.
왜 중요한가
- Erlang 컴파일러의 앞단(구문 분석)을 건너뛰게 되어 Erlang에서 돌아가는 Gleam 프로젝트의 빌드 시간이 크게 줄었습니다.
- 충돌 보고서와 스택 트레이스의 줄 번호가 근처 함수가 아니라 Gleam 소스의 정확한 줄을 가리킵니다.
- edb 같은 디버거를 온전히 지원할 길도 열렸지만, 아직 구현되지는 않았습니다.
핵심 내용
- 추상 형식(abstract forms)은 Erlang 컴파일러가 쓰는 중간 표현으로, 메타데이터를 붙인 문법 트리입니다.
- BEAM 바이트코드를 직접 만들지 않은 이유는 바이트코드가 Erlang 릴리스마다 바뀌어 계속 따라가야 하기 때문입니다.
- 후원으로 운영하는 프로젝트라 비용 대비 효과가 가장 좋은 지점을 골랐고, Elixir가 이미 검증한 방식을 따랐습니다.
- 함수 100개짜리 모듈 100개로 잰 벤치마크에서 v1.17.0보다 크게 빨라졌지만, 팀은 인위적인 측정이라고 단서를 달았습니다.
- 이 밖에 JavaScript 패턴 매칭 코드 최적화, 언어 서버의 레이블 지원, 오류 메시지 개선이 들어갔습니다.
HN 반응
- 추상 형식은 Elixir를 비롯한 대부분의 BEAM 언어가 쓰는 대상이고, 안정성 보장이 없는 Core Erlang은 드물게 쓰인다는 보충 설명이 나왔습니다.
- Rust 개발자에게 Gleam이 쉬운지를 두고 논쟁이 이어졌고, Gleam 제작자(lpil)는 두 언어가 매우 다르며 OCaml·Elm·F#에 더 가깝다고 반박했습니다.
궁금해하실 분들을 위해 설명하면, Erlang 추상 형식(abstract form)[1]은 Erlang 컴파일러 등이 사용하는 AST 표현입니다. [1]을 보면 알 수 있듯이 이 표현은 원래 Erlang 항(term)으로 이루어져 있고, 이를 다루는 루틴도 표준 라이브러리에서 쉽게 쓸 수 있습니다. 필요할 때 정말 편합니다. Elixir가 컴파일되어 내려가는 대상도 이것이고, 파스 트랜스폼(parse transform)이 다루는 표현도 이것입니다. 기본 Erlang에서는 파스 트랜스폼으로 문법 설탕(syntactic sugar)을 구현하는데, 대표적으로 qlc[2]와 merl[3]의 좀 앙증맞은 패턴 매치 몇 가지가 그렇습니다(merl도 바로 이 표현을 조작해서 일을 하니, 아주 메타적이죠!).
사실 대부분의 BEAM 언어는 Erlang 추상 형식에 정착했습니다. Core Erlang이 전통적인 함수형 IR에 더 가까워 보여서 더 흔할 것 같지만, 실제로 그렇게 하는 곳은 LFE 정도뿐입니다. 릴리스마다 바뀌고 안정성도 전혀 보장되지 않는 움직이는 표적이기 때문입니다.
1: https://www.erlang.org/doc/apps/stdlib/erl_parse.html#t:abstract_form/0
2: https://www.erlang.org/doc/apps/stdlib/qlc.html#q/2
3: https://www.erlang.org/doc/apps/syntax_tools/merl.html
LFE는 예전에는 Core Erlang으로 컴파일했지만, 지금은 추상 형식으로도 컴파일합니다.
https://github.com/lfe/lfe/blob/387d0aa1092bdda0ca17481869e833b24f11971a/src/lfe_codegen.erl
https://github.com/lfe/lfe/blob/387d0aa1092bdda0ca17481869e833b24f11971a/src/lfe_translate.erl
Erlang은 2008년에 처음 알게 된 뒤로 줄곧 좋아한 런타임인데, 문법은 끝내 배울 시간을 내지 못했습니다. 그래도 Elixir와 Gleam 덕분에 몇 년째 Erlang VM을 만지작거리고 있습니다. Gleam이 성숙해져 가는 모습을 보니 기쁩니다.
저는 올해 Erlang을 처음 배웠는데 문법이 마음에 듭니다. 표현식을 마침표로 끝내는 건 익숙해지는 데 시간이 걸렸지만, 대문자로 시작해서 구분되는 변수, 다시 바인딩할 수 없는 변수, 함수 절 사이의 세미콜론, 상용구(defmodule 같은 것)가 없다는 점, 몇 개 안 되는 end 키워드, 표현식을 구분하는 쉼표 같은 것들이 전부 모여서 쓰기 즐겁고 인체공학적인 언어를 만든다고 생각합니다.
아직 Gleam은 써 보지 못했습니다. 단순함에 집중하는 점은 흥미롭지만, 언어의 규모를 줄이려고 함수 헤드의 패턴 매칭 같은 편의 기능을 포기했다고 알고 있어서 조금 꺼려지기도 합니다. 다음에 새 프로젝트를 처음부터 시작할 때 한번 써 볼 생각입니다.
제가 써 본 함수형 언어 중에서는 Erlang이 제일 좋습니다. 매처(matcher)나 가드(guard) 같은 것들이 아주 잘 만들어져 있어요. 그런데 저는 함수형 자체가 별로입니다. 한때는 멋지다고 생각해서 몇 년 동안 Erlang으로 진지하게 해 봤지만, 결국 많은 용도에는 맞지 않는다고 결론 내렸습니다. 모든 반복을 재귀로 하는 깔끔함도 어느 순간 시들해지고 그냥 피곤해집니다. 대학 알고리즘 과제도 아니고, 과제라 해도 DP/메모이제이션이 필요한 경우가 많잖아요.
수정: 순수* 함수형입니다.
의외네요, ㅋㅋ 그래도 믿습니다. Elixir의 리스트 컴프리헨션에는
:reduce옵션이 있어서 값을 누적하는 게 좀 더 "익숙하게" 느껴지는데, 저는reduce대신 이걸 자주 씁니다.Erlang(그리고 Gleam)에 이런 게 있는지는 모르겠어요.
Erlang에서 이미 리스트가 있다면 map이랑 foldl/foldr 리듀서를 쓸 수 있습니다. 하지만 범용 for 루프는 없어요. 꼬리 재귀를 쓰라는 겁니다.
그리고 솔직히 저는 reduce도 싫습니다. 얼마 전 HN에 올라온, 개발자들이 reduce를 좋아하지 않는다는 글에 동감합니다. 그냥 리스트에 값을 넣고 싶을 뿐인데, 이 언어에서는 reduce가 정확히 어떻게 동작하는지까지 확인해야 하니까요.
네, 제 말은 좀 더 동적으로 보이는 루프의 대안으로 그렇다는 거였어요. 참고로 Elixir의
for는 for 루프가 아니라 리스트 컴프리헨션이라서 헷갈립니다.<-도 사실은 매치 연산자라서 for 루프처럼 생각하면 혼동하기 쉽고요. 왜for로 했는지는 저도 잘 모르겠어요. 한때는lc였거든요. 그래도for가lc보다는 낫다고 봅니다, 헤헤.아무튼 저도 reduce가 싫지는 않지만, 더 고수준 버전이 있으면 그쪽을 쓰려고 합니다. reduce의 유일한 문제는 가끔 파라미터 순서를 까먹는다는 건데, 리스트 컴프리헨션은 누산기에 이름이 붙으니까 그래서 좋아합니다.
저도 마찬가지예요. 다들 Elixir를 더 좋아하는 것 같아서 제가 별난 사람인 줄 알았는데, 저는 Erlang과 그 문법이 좋습니다. 직장에서 쓸 기회가 더 많았으면 좋겠는데, 지금까지는 취미 프로젝트나 가지고 노는 용도로만 썼네요.
우리 같은 사람이 수십 명은 있다고요! Erlang은 훌륭한 언어인데, Elixir는 왠지 좀 허술하고 매크로가 과하다는 느낌이 들어요.
Erlang 이전에는 어떤 배경이셨어요? 저는 Ruby 출신이라 Elixir가 Erlang보다 훨씬 집처럼 편하게 느껴졌거든요.
저는 운 좋게도 몇 년 동안 Erlang을 업무로 썼습니다. 동의합니다. 문법이 꽤 편안합니다.
Rust를 알고 좋아한다면 Gleam은 금방 익힐 수 있을 테고, 그렇지 않다면 Elixir를 써 보라고 하고 싶습니다. Elixir는 며칠이면 언어 대부분을 배울 수 있고, 첫날부터 BEAM과 수퍼비전(supervision) 개념을 직접 실험해 볼 수 있습니다. 액터/프로세스는 어디서나 볼 수 있는 동시성 모델은 아니지만 흥미로운 문제를 잘 풀어 주는 모델이라서, 학습 삼아 해 볼 만한 가치가 있다고 생각합니다.
그렇지 않다고 생각합니다. Rust와 Gleam은 정적 타입 시스템이 있다는 것 말고는 공통점이 거의 없고, 타입 시스템도 서로 많이 다릅니다.
https://gleam.run/frequently-asked-questions/#How-does-Gleam-compare-to-Rust
Rust 개발자에게는 Elixir보다 Gleam의 문법이 더 익숙하다고 생각하지 않으시나요? 아니면 타입 시스템이 더 친숙하게 느껴질 수 있다는 것도요?
Rust 프로그래머라면 Elixir보다 Gleam을 더 좋아할 가능성이 높다고는 생각합니다. 하지만 Gleam과 Rust 사이에 큰 연관이 있다고는 보지 않고, 특히 이미 Rust 프로그래머일 때만 Elixir 대신 Gleam을 고려해야 한다고는 전혀 생각하지 않습니다.
제가 이렇게 말했습니다.
그리고 당신은 이렇게 생각하시고요.
지금 말꼬리를 잡고 있는 건 아닐까요?
당신은 이렇게 말했죠.
이건 너무 강하고 좁은 주장입니다. Gleam은 정적 타입에 가비지 컬렉션이 있는 언어라서 Rust보다는 OCaml에 가깝습니다. 동적 타입이 좋다면 Elixir고요.
Elixir는 이제 점진적 타입(gradual typing)이 도입되었다는 말씀을 안 드릴 수 없죠: https://news.ycombinator.com/item?id=48388324
솔직히 점진적 타입은 악몽처럼 들려요. "올바르게"의 정의가 뭐든 간에 제대로 구현하는 건 아마 불가능할 겁니다.
아뇨, 타입 시스템 구현을 까다롭게 만드는 건 점진적이라는 측면이 아닙니다. 게다가 저희는 Elixir에 어떻게 타입을 붙이고 있는지, 그리고 저희 접근법이 왜 건전한(sound) 방식인지를 다룬 논문을 몇 편 발표했습니다.
제가 오해하고 있을 가능성은 충분히 있습니다! 제가 말하려는 건 대체로, Gleam은 Rust와 전혀 다른 언어라서 Rust와 관련된 이유로 Gleam을 고르거나 피하면 충분한 정보를 바탕으로 한 결정이 아닐 수 있다는 겁니다.
Gleam과 가장 비슷한 언어는 Standard ML, OCaml, Elm, F# 정도일 겁니다.
두 언어를 기술적으로 비교하는 발언으로 해석하고 계신 것 같은데, 제가 보기에 ch4s3는 프로그래머가 한 언어에서 다른 언어로 넘어가는 전반적인 능력을 이야기한 겁니다.
즉 기술적으로 비슷한 언어는 아니지만, Rust를 안다면 Gleam으로 꽤 빨리 넘어갈 수 있을 거라는 뜻이죠.
네, 제가 하는 말은 두 언어가 워낙 달라서 Rust에서 Gleam으로 넘어가기가 쉽지 않다는 겁니다. "Rust에서 하던 방식대로 Gleam에서 X를 하려면 어떻게 하나요"는 둘이 비슷하다고 생각했던 신규 사용자들이 가장 흔히 하는 지원 질문 유형 중 하나입니다.
두 언어가 워낙 달라서 일하는 방식이 꽤 다르다는 걸 이해하는 데 시간이 걸릴 수 있습니다: https://gleam.run/frequently-asked-questions/#How-does-Gleam-compare-to-Rust
그런데 Rust의 여러 부분에 영감을 준 언어가 어느 쪽인지 맞혀 보세요.
ML 계열이 Rust에 큰 영감을 준 건 분명하지만, 결과물은 매우 다릅니다.
저는 Rust를 쓰는데도 Gleam보다 Elixir를 선호합니다. Elixir에는 Lisp 스타일 매크로가 있고(Gleam에는 없습니다), OTP를 직접 쓸 수 있고, 훌륭한 IEx REPL이 있고(Gleam에는 대응하는 게 없습니다), 제가 써 본 최고의 웹 프레임워크인 Phoenix가 있으니까요.
네, 제 말이 바로 그겁니다! Rust 사용자라고 해서 Gleam을 더 좋아하거나 심지어 이해하게 되는 것도 아니라는 거죠.
저는 이런 말이 유니온, 레코드, 패턴 매칭을 아는 사람이라면 유니온, 레코드, 패턴 매칭이 있는 다른 언어도 더 쉽게 익힌다는 뜻이라고 생각합니다.
네, 제 말이 본질적으로 그겁니다. 겹치는 부분이 많다는 걸 보여 주는 공식 치트시트[1]도 있고요.
[1] https://gleam.run/cheatsheets/gleam-for-rust-users/