Elixir로의 전환
(leemeichin.com)- Ruby 백엔드 개발자가 Elixir를 주 언어로 쓰는 회사에 합류한 뒤, Ruby 영향을 받은 문법 덕분에 Elixir/Erlang 경험이 없어도 비교적 빠르게 적응함
- Elixir는 Ruby처럼 문법적 재미와 DSL 친화성을 제공하면서도, 클래스·인스턴스·상속이 없는 불변 함수형 모델로 상태 관리 부담을 줄임
- Ruby DSL이 런타임에서 객체와 메서드를 동적으로 정의하는 데 기대는 반면, Elixir DSL은 컴파일 타임 매크로로 런타임 코드를 생성해 오류를 더 일찍 드러냄
- 파이프라인 문법,
with표현식,{:ok, result}형태의 Result 튜플은 정상 흐름과 복구 가능한 오류 처리를 분리하는 데 유용함 - Ruby에서 예외와
nil/false, 객체 내부 오류 상태에 기대던 방식보다 Elixir의 명시적ok/error반환이 더 만족스럽고, 3개월 사용 후에도 작성 경험이 즐겁다고 봄
Ruby 경험자가 Elixir에 적응한 방식
- 새 직장은 백엔드의 주 언어로 Elixir를 사용하며, 시작 전에는 Elixir나 Erlang 코드를 작성한 경험이 없었음
- Erlang은 Prolog를 조금 다뤄본 경험 때문에 낯설지 않았고, Elixir는 Ruby에서 강하게 영향을 받은 문법 덕분에 빠르게 익힐 수 있었음
- 아직 모범 사례, 아키텍처, 더 낮은 수준의 Erlang 개념에 전문적이지는 않지만, 이런 부분이 Elixir 진입을 막지는 않았음
- Elixir는 Ruby가 주는 프로그래밍의 재미를 유지하면서도 OOP 언어에서 흔한 상태 관련 함정을 줄이는 언어로 받아들여짐
- 클래스, 인스턴스, 상속이 없음
- 불변성과 함수형 스타일을 기반으로 함
- 정적 타입 시스템에 묶이지 않음
- 가변 상태를 가진 코드는 유지보수가 훨씬 어렵고, 상태가 함수 로컬인지 인스턴스인지 클래스인지 항상 명확하지 않음
- Ruby에서는 모든 것이 객체라 객체마다 상태를 가질 수 있고, 직관적인 DSL을 만들려는 과정에서 함수·인스턴스·클래스 수준 상태가 섞이기 쉬움
- Ruby의 Eigenclass는 클래스의 클래스와 같은 개념이며, 한 인스턴스가 eigenclass를 수정해 다른 인스턴스들의 상태에도 영향을 줄 수 있음
DSL과 컴파일 타임 매크로의 차이
- Elixir도 Ruby처럼 DSL을 지원하고 문법도 익숙하지만, 핵심 차이는 DSL이 만들어지는 시점에 있음
- Elixir DSL은 컴파일 타임 매크로이며 런타임 코드를 생성함
- Ruby DSL은 런타임을 수정해 객체와 메서드를 동적으로 정의하는 방식에 의존함
- Elixir 매크로를 잘못 사용하면 라이브러리에서 오류가 나고, 생성된 매크로 코드가 잘못되면 컴파일러 오류가 발생함
- 이런 오류가 컴파일 타임에 드러나므로 테스트는 실제 애플리케이션 로직에 더 집중할 수 있음
파이프라인과 with가 만드는 흐름
- Elixir의 파이프라인 연산자
|>는 유용하지만, Clojure의 threading 연산자에도 장점이 있음- Clojure의
->는 Elixir의|>처럼 이전 표현식 결과를 다음 함수의 첫 번째 인자로 넣음 - Clojure의
->>는 이전 결과를 다음 함수의 마지막 인자로 넣음 - 이 차이는 작아 보여도 익명 함수를 쓰지 않고 상호운용 코드를 작성할 때 유용함
- Clojure의
- Elixir의
with표현식은 Haskell·Lisp의let을 떠올리게 하면서otherwise를 내장한 것처럼 동작함- 복잡한 함수를 위쪽의 정상 경로와 아래쪽의 오류 처리로 나눌 수 있음
{:ok, result}에 패턴 매칭할 수 없으면 복구 가능한 오류로 다룰 수 있어 Result 튜플과 잘 맞음
명시적 오류 반환이 주는 장점
- Ruby에서는 제어 흐름에 예외를 쓰는 일이 흔하고, 명시적 오류 타입 대신 성공 결과,
nil,false, 또는 객체 내부 오류 상태에 의존하는 경우가 많음- 예를 들어
model.update(params)는 실패 시false를 반환하고model.errors에 실패 이유를 저장함 - 이는 다시 가변 상태 문제로 이어짐
- 예를 들어
Result/Either모나드처럼ok/error또는left/right를 반환값으로 명시하는 방식이 Ruby식 예외 흐름보다 더 만족스럽게 느껴짐- 3개월 동안 Elixir를 사용한 뒤에도, Elixir는 Ruby가 보여준 “프로그래밍은 재미있다”는 감각을 이어가는 언어로 남아 있음
댓글과 토론
Hacker News 의견들
-
최근 몇 주 동안 거의 매일 Elixir YouTube 영상을 보고 있음
얼마 전 Elixir 컨퍼런스가 있었던 듯하고, 몇 개를 보고 나니 YouTube가 계속 Elixir 콘텐츠를 추천해 줌
Erlang의 아이디어는 좋아하지만, 예전에 ejabberd 채팅 서버 같은 걸 다룰 때는 너무 특이하다고 느꼈음
Joe Armstrong 영상을 볼 때마다 과소평가된 천재였다는 생각이 들고, 격리된 프로세스와 메시지 전달, 즉 액터 모델은 분산 프로그래밍의 미래 후보로 좋아 보임
Crockford의 새 Misty 언어 영상 https://www.youtube.com/watch?v=R2idkNdKqpQ에서도 질문이 Elixir/Erlang 쪽으로 갔다는 점이 인상적이었음
다만 LiveView와 LiveBook의 “마법”을 보면 아직 100% 확신은 안 듦. 너무 좋아 보이는 건 대개 함정이 있었고, GenServer 같은 낯선 용어와 플랫폼 고유 지식은 한 방향 포털로 들어가는 느낌이라 다른 곳으로 이전 가능한 지식인지 모르겠음
Go도 꽤 괜찮은 동시성 이야기를 갖고 있는 상황에서, 작은 커뮤니티의 언어와 플랫폼은 채용·라이브러리·자료 면에서 리스크가 큼. 그래도 Elixir 커뮤니티가 이룬 성과는 정말 인상적이고, 타입 시스템 구현이 어떻게 될지도 기대됨- 동시성 쪽은 조금 과하게 걱정하는 것 같음. 프로세스가 무엇이고
send가 어떻게 동작하는지만 보면 됨
GenServer는 수없이 직접 작성하게 될 패턴을 자연스럽게 일반화한 것이고, 액터 지식은 다른 곳으로도 옮겨갈 수 있음
Go에도 액터 추상화를 구현한 라이브러리가 있고, 프로세스 우편함은 Go의 채널처럼 메시지 큐라고 보면 됨. 차이는 인터프리터와 프로세서 양보가 어떻게 동작하느냐 쪽에 있음
반면 LiveView는 마법처럼 보이는 게 맞음. 실제로 많은 움직이는 부품이 맞물려 있고, 여러 커뮤니티에서 지난 10년간 쌓아온 작업의 결과라 추상화는 많아도 아이디어 자체는 성숙함
Riak 같은 것은 시대를 앞서갔고, GenServer를 만들듯 견고한 분산 시스템을 만들 수 있다는 아이디어를 거의 갖고 있었음 - Elixir를 초기 커뮤니티 밖으로 밀어내야 할 실제 필요가 있고, 지금 어느 정도 그렇게 되고 있다고 느낌
Chris와 José 모두 초기 Elixir圈 밖에서 노출을 늘리려 많이 움직이고 있고, 커뮤니티 안의 사람들도 각자 수준에서 같은 일을 하고 있음
채용은 생각만큼 복잡하지 않음. 이 언어로 일하고 싶어 하는 사람이 꽤 많고, 좋은 개발자는 꽤 빨리 따라잡음
그래도 틈새 언어라는 면은 길들여야 함. 언어 자체에는 리스크지만, 단기적으로는 틈새 기술에 능숙하다는 보상이 되기도 함 - 오히려 반대로 느낌. Elixir는 배우고 나면 마법이 덜해짐
대부분이 무엇을 하는지 보고 이해할 수 있고 코드를 읽을 수 있음. JavaScript는 그렇지 않은 경우가 많음
채용과 튜토리얼 찾기가 어렵다는 점은 맞음. 예를 들어 Elixir로 내가 하려는 일을 이미 해본 사람을 찾기는 JavaScript보다 훨씬 어려움
라이브러리는 큰 문제는 아니었고 꽤 많음. API를 다룰 때 JavaScript나 Python용 클라이언트 라이브러리는 있어도 Elixir용은 없는 경우가 있는데, 보통은 HTTP 클라이언트만으로 API를 쓰는 게 그리 어렵지 않음 - Ruby에서 Elixir로, 다시 Node.js로 왔음
지금 Node.js를 쓰는 건 원해서가 아니라 해야 해서임. 써보고 나니 새 프로젝트를 Node.js로 시작할 일은 절대 없겠다고 생각함
Node.js의 동시성 제어, 확장성, 신뢰성·관측성 도구는 BEAM 생태계보다 한참 뒤처져 있음
사람들이 Elixir를 너무 좋아서 믿기 어렵다고 느끼는 건, 회복탄력성을 위해 설계되지 않은 플랫폼에 너무 익숙해서일 때가 있다고 봄 - LiveView와 LiveBook의 마법이 뭔지 잘 모르겠음
LiveBook은 차이 계산이나 데이터 압축의 정확한 세부를 제외하면 올바른 정신 모델을 갖기 꽤 쉬움. 급하면 WebSocket 메시지를 들여다봐도 무슨 일이 벌어지는지 꽤 파악 가능함
LiveView는 놀랄 만큼 직관적임
- 동시성 쪽은 조금 과하게 걱정하는 것 같음. 프로세스가 무엇이고
-
Elixir/Erlang의 큰 매력은 웹 서버 맥락에서 백그라운드 작업을 정말 쉽게 돌릴 수 있고, 블로킹 입출력이 서버 전체를 멈출 걱정이 없다는 점임
이전 직장에서 웹훅 핸들러 안에서 HTTP 요청을 많이 해야 했는데, 동시에 충분히 많이 발생하면 운영체제 프로세스가 다 바빠져서 사이트 전체가 죽곤 했음
그럴 때 Elixir였다면 Task 하나 띄우고 넘어가면 됐을 텐데 하고 절실히 생각했음- 최근 새 서비스에서 Rust에서 Elixir로 전환했는데, 이 점이 꽤 큰 이유였음
Tokio 비동기 프로세스 사이에서 객체를 공유한다는 아이디어는 Elixir의 일급 기능에 비해 어렵게 느껴졌음. 불가능하거나 본질적으로 어렵다는 건 아니지만, 올바르게 만들기가 더 어려워 보였음 - 그런 방식으로 백그라운드 작업이 쉬워지는 건 별로 원하지 않음
백그라운드 작업은 HTTP 요청과 다른 컴퓨트 용량 위에 두고 싶음. 자원 사용 양상이 다르고, 재시도·오류 처리·역압력을 명확히 하기 위해 상태나 큐를 앞단에 두고 싶기 때문임
물론 이런 복잡성이 없고 같은 프레임워크가 전부 처리해 주면 좋겠지만, 프로그래밍 언어나 런타임만으로 거기까지 가기는 어렵다고 봄. 모니터링과 운영 절차까지 해법의 일부여야 함
- 최근 새 서비스에서 Rust에서 Elixir로 전환했는데, 이 점이 꽤 큰 이유였음
-
Sasa Juric의 The Soul of Erlang and Elixir 발표는 이 언어가 얼마나 강력할 수 있는지 잘 보여줌
https://www.youtube.com/watch?v=JvBT4XBdoUE&t=4- Elixir가 궁금해서 이번 주 초에 이 발표를 봤음
“실패하게 두라”거나 “BEAM은 동시성을 제대로 한다”는 말을 들었지만 아직 감이 안 오는 사람에게 강력히 추천함. 정말 대단한 발표임
- Elixir가 궁금해서 이번 주 초에 이 발표를 봤음
-
1991년부터 매일 Erlang으로 프로그래밍해 왔고, 계속 그럴 생각이었음
그동안 Haskell, Rust, Elixir 등도 해봤음. 강한 타입은 매우 가치 있지만, 결국 어디서 가장 재미를 느끼느냐로 귀결됨
Erlang은 가장 큰 즐거움을 주는 프로그래밍 언어이고, 몇 년 뒤 은퇴할 때까지는 적어도 계속 Erlang을 해킹할 계획임- 어떤 종류의 프로젝트가 작업하기 가장 재미있었는지 궁금함
-
Elixir를 알게 된 지 1년 조금 넘었는데, 배우고 여러 구성요소에 넣어보는 과정이 꽤 즐거웠음
가장 큰 즐거움은 BEAM/OTP만으로 기본 제공되는 것들이 많다는 데서 옴
함께 일하던 팀의 누군가가 “BEAM/OTP는 복잡한 부분을 뺀 k8s 같다”고 말한 적이 있음- 다른 설명이 안 통할 때 팀에 그렇게 설명함
몇 년 동안 Elixir를 배웠지만, 최근 내가 설계한 OpenShift, 즉 k8s 마이그레이션 때문에 이점이 어느 정도 희석됐음
k8s가 없었다면 훨씬 더 매력적인 제안이었을 것임. 대신 기존 개발 스택을 유지하고, OTP로 했을 일을 k8s 개념으로 달성했음. 물론 트레이드오프는 달랐음
- 다른 설명이 안 통할 때 팀에 그렇게 설명함
-
“Elixir는 Ruby 같은 재미있는 언어를 주면서, 객체지향 언어의 상태 관련 발목지뢰는 빼준다. 클래스도, 인스턴스도, 상속도 없고, 불변·함수형이며 정적 타입 시스템에 얽매이지 않는다”
이걸 원하지만 타입도 있었으면 함. 큰 코드베이스에서는 강한 타입이 맞다고 확신함. 많은 마법을 드러내 주고 추론하기 쉬워지기 때문임- Elixir에서는 Ruby나 Python 같은 다른 동적 언어, 혹은 가변성을 가진 정적 언어보다 이 문제가 덜함
함수의 패턴 매칭/단일화 덕분에 데이터가 오갈 때 어떤 형태인지 꽤 잘 알 수 있음. 그래도 불안하면 Dialyzer가 있음
Dialyzer는 Erlang/Elixir용 정적 분석 도구임. 표준 Erlang 릴리스에 포함되어 있고, “DIscrepancy AnaLYZer for ERlang programs”의 약자임
단일 Erlang 모듈이나 애플리케이션 묶음에서 타입 오류, 죽은 코드, 불필요한 테스트 같은 불일치를 찾아냄. 올바른 프로그램을 기반으로 타입을 추론하며 타입 주석이 필수는 아니지만, 있으면 더 나은 경고를 제공함
https://fly.io/phoenix-files/adding-dialyzer-without-the-pai... - 타입은 오고 있음. 너무 멀지 않았으면 좋겠음
https://elixir-lang.org/blog/2023/06/22/type-system-updates-... - 여기서는 반대 입장임. Elixir의 GenServer에는 상속이 있었으면 정말 좋겠음
GenServer는 객체에 가깝고, 단순히 연관 함수가 붙은 구조체가 아니라 메시지를 통해 상호작용함
프로젝트의 GenServer에 공통 등록, 메모리 설정, 로깅 같은 기본 템플릿을 두고, 이를 세 가지 변형의 특정 작업에 맞게 특화할 수 있으면 좋겠음
Elixir는 이 문제를 매크로 코드 생성으로 메우지만 썩 좋지는 않음. 내 GenServer들은 사소한 반복으로 가득함 - 그렇다면 Gleam이 마음에 들 수도 있음. 비슷한 느낌이 있어 보임
- Elixir에서는 Ruby나 Python 같은 다른 동적 언어, 혹은 가변성을 가진 정적 언어보다 이 문제가 덜함
-
“Ruby에서는 제어 흐름에 예외를 쓰는 일이 흔하다”는 말은 그냥 틀렸다고 봄
그 단락 뒤의 예시는 Rails의update메서드인데, 표준적인 Rails 예제와 생성기에서 쓰는 방식은 예외를 쓰지 않는update버전임- 예제는 특히 배우는 사람을 위해 복잡성을 제한하는 단순한 방식을 보여줌
어느 정도 규모가 있는 라이브러리나 애플리케이션은 충분히 복잡해지면 설명적인 오류 클래스로 넘어감. ActiveRecord처럼 상태 기반 오류 처리는 고통스럽기 때문임
Ruby에 이제 어느 정도 패턴 매칭이 있으니, 제어 흐름용 예외를 대체할 수 있다고 봄. 예외를 던지는 대신 클래스 자체를 반환하고 거기에 매칭하면 됨 - 2004년부터 Ruby를 써온 입장에서는 전혀 틀렸다고 느끼지 않음
많은 gem이 제어 흐름에 예외를 쓰고, 내가 작성하거나 유지보수한 앱과 라이브러리에서도 흔했음 - Ruby와 Rails는 같지 않음
Rails에는 오류 시 예외를 던지는bang!메서드가 많음. 하지만 일반적으로 Ruby는 Python처럼 제어 흐름에 예외를 쓰는 일이 실제로 흔함
- 예제는 특히 배우는 사람을 위해 복잡성을 제한하는 단순한 방식을 보여줌
-
F#에서 왔고 Elixir는 조금만 만져봤는데, 언어가 아쉽게도 약간 혼란스러움
예를 들어 함수 시그니처가 이름과 매개변수 개수만 드러내고 타입은 드러내지 않아서, 큰 라이브러리에서는 무엇이 어디로 들어가는지 머릿속으로 파악하기 꽤 어려움
잘못된 매개변수를 넣었다는 사실도 코드를 실행하고 디버깅한 뒤에야 보이곤 함. F# 같은 엄격한 타입 언어에서는 이런 문제가 코딩 중 바로 잡히므로 시간과 노력이 크게 줄어듦
그래서 Erlang 환경을 제외하면 Elixir의 주된 매력이 무엇인지 궁금함- Elixir의 정적 타입은 활발히 개발 중임
https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...
현재는 동적 언어가 맞고, 그래서 언어 자체에서 타입 정보를 많이 얻지는 못함. 아직 정적 타입 언어는 아님 - Elixir 자체에도 좋아할 부분이 많음. 기사에서 말한 것처럼 Ruby에서 영감을 받은 문법은 보기 좋고 익히기 쉬움
하지만 내가 써본 다른 언어들보다 Elixir에 우위를 주는 것은 정말로 BEAM/OTP임
경험상 다른 언어라면 라이브러리로 가져와야 할 많은 기능이 기본 포함되어 있음. 그런 라이브러리들은 복잡성을 크게 늘리고 때로는 학습 곡선도 큼. 비동기 관련 기능이 좋은 예임
Elixir를 한동안 쓰면서 이런 내장 도구와 추상화를 익히면 생태계 전체의 힘을 이해하게 된다고 봄 - 언어만 놓고 보면 F#보다 업그레이드라고 부르지는 않겠음. 주관적이지만
주된 매력은 아마 Ruby 느낌의 문법으로 BEAM 위에서 돈다는 점일 것임. BEAM은 꽤 큰 장점이라고 봄
F#의 타입과는 잘 비교되지 않지만, typespec은 쓸 수 있음: https://hexdocs.pm/elixir/1.15.7/typespecs.html - .NET은 꽤 많이 써봤고 F#은 가끔만 써봤는데, Elixir의 매력은 몇 가지가 있음
예를 들어 Axon, Nx, BumbleBee와 함께 기계학습이 네이티브로 통합되고 GPU 컴파일까지 가능한 흐름이 있음: https://www.youtube.com/watch?v=HK38-HIK6NA
커뮤니티에서 이쪽을 실제로 강하게 밀고 있음
LiveView의 동작 방식도 매력적임. 다만 .NET에는 Blazor가 있음
Elixir는 임베디드의 Nerves나 스크립팅 등 다양한 맥락에서 쓸 수 있음. F#도 스크립팅에 쓸 수는 있는 것으로 앎
전반적으로 BEAM과 관련 구조들이 강점이고, 패키지 관리자 Hex도 정말 좋음. 물론 .NET도 도구는 좋음 - 함수 매개변수에 타입 힌트가 없다는 점이 Elixir에서 생산성에 가장 큰 타격임
typespec, 패턴 매칭, 가드로 버틸 수는 있지만 꽤 번거롭고, 적어도 커뮤니티 주도의 JetBrains 플러그인에서는 IDE/IntelliSense 친화적이지 않음
- Elixir의 정적 타입은 활발히 개발 중임
-
시작할 때가 아니라 2년 써본 뒤에 써보는 게 좋겠음
새로운 것은 처음에는 항상 흠 없는 반짝이는 만능 해결책처럼 보임- 글쓴이이고, 허니문 기간이라는 개념에는 익숙함
첫인상도 나름의 자리가 있고, 내가 제공하는 것도 그것임. 대상은 Elixir/Erlang 전문가가 아니라 Ruby 전문가들이기 때문임 - 박사 과정 시작 전까지 그보다 더 오래 전문적으로 Elixir 개발자로 일했음
Elixir는 그때도 지금도 보석 같음. 완벽하지는 않지만, 놀랄 만큼 잘 버팀
- 글쓴이이고, 허니문 기간이라는 개념에는 익숙함
-
2007년쯤부터 Ruby를 써왔음
다음 “멋진” 것으로 갈아타려 할 때마다 결국 Ruby로 돌아왔는데, Ruby를 좋아하고 Ruby로 가장 빠르게 일할 수 있으며 그게 전부였기 때문임
그런데 최근 Elixir 이야기가 훨씬 더 많이 보이고, 겉모습과 느낌이 마음에 듦. 곧 새 사이드 프로젝트를 시작해서 한번 써봐야 할 것 같음