크리스 젠킨스: 오늘 저는 Elixir 언어의 창시자인 José Valim과 함께 합니다. 잘 지내시죠?
José Valim: 네, 덕분에 잘 지내고 있습니다. 초대해 주셔서 감사합니다.
크리스 젠킨스: 여기 와주셔서 감사합니다. 10년, 아니 12년 동안 언어 설계자로 활동하셨죠. 도널드 크누스보다는 젊지만, 꽤나 원로급 언어 설계자라고 할 수 있겠네요. 중견 언어 설계자라고 할까요?
José Valim: 네, 그렇죠. 이 특별한 분야에서 어떤 중년의 위기가 찾아올지 궁금하네요. 나중에 알려주셔야 할 겁니다. 10년, 12년 전으로 돌아가 볼까요? 2012년에 Elixir를 만드셨죠. 외부에서 보기에는 주된 동기가 Erlang은 좋아하지만 문법은 싫어서, Erlang에 일반적인 문법을 적용하고 싶어서였다는 인상을 받았습니다. 맞나요? 아니면 좀 더 자세히 설명해주시겠어요?
José Valim: 반은 맞습니다. 많은 사람들이 문법에 대해 생각합니다. Elixir와 Erlang의 문법이 너무 다르기 때문에 가장 먼저 떠오르는 것이죠. Erlang 문법은 Prolog에서 유래했기 때문에 특이합니다. Elixir는 Ruby를 비롯한 다른 동적 언어들과 더 가깝습니다. Python에서도 일부 영향을 받았죠. 문법은 많은 사람들이 가장 먼저 떠올리는 부분입니다. 하지만 저에게는 문법이 큰 걱정거리가 아니었던 것 같습니다. 실제로 Erlang 문법의 간결함이 마음에 드는 부분도 있거든요. Prolog이나 다른 함수형 프로그래밍 언어들처럼 구두점을 많이 사용해서 시끄럽다는 느낌도 있지만, 매우 간결하고 Elixir보다 우아하게 처리하는 부분도 있다고 생각합니다.
크리스 젠킨스: 예를 들어 설명해주시겠어요?
José Valim: 예를 들어, 함수 정의를 보면 Elixir와 Erlang 모두 여러 절을 가질 수 있습니다. 여러 절을 나란히 작성해야 할 때 Erlang이 Elixir보다 훨씬 간결하다고 생각합니다. 실제로 저는 Erlang 쪽을 선호합니다.
크리스 젠킨스: 알겠습니다.
José Valim: 둘을 나란히 비교해 보면 "아, Erlang 쪽이 덜... 확실히 입력해야 할 게 적네. 더 간결하군."이라고 생각하게 될 겁니다. 저에게 Elixir는 Erlang에 대한 애정에서 시작된 프로젝트였습니다. Erlang에 부족하다고 느꼈던 기능들이 있었지만, 꼭 언어 자체의 문제는 아니었습니다. 그 부분에 대해서는 나중에 이야기할 수 있을 것 같습니다. 모든 것이 기본적으로 UTF-8로 인코딩된다는 점이 그 예입니다. 제가 Erlang을 처음 사용하기 시작했을 때 Erlang은 특정 인코딩을 사용했기 때문에 특수 표기법을 추가하지 않으면 제 이름조차 제대로 쓸 수 없었습니다. 제 이름에는 악센트가 있는 "é"가 있거든요. 문법 수준에서 몇 가지 문제가 있었지만, 주로 추상화 수준에서 문제가 있었습니다. 특히 두 가지가 있었는데, 하나는 메타 프로그래밍이고 다른 하나는 다형성의 일부 형태였습니다. Elixir는 2011년에 시작되었습니다. 그때는 주로 아이디어를 가지고 놀고 있었죠. Elixir의 첫 번째 버전은 실제로 객체 지향적이었습니다. 주로 Ruby를 사용했던 경험 때문이었죠. "이런 기능들이 부족하네."라고 생각했고, "내가 부족하다고 느끼는 것은 객체인 것 같아."라고 생각했습니다. 스포일러지만, 결국 객체가 부족한 것은 아니었습니다. 하지만 그때는 그렇게 느꼈습니다. 그래서 여러 가지를 시도해 보았고, 1년 후에 디자인을 완성했습니다. 이 과정에서 중요했던 점은 메타 프로그래밍을 원했다는 것입니다. 메타 프로그래밍을 하는 방법은 여러 가지가 있습니다. 이 과정을 거치면서 Lisp 스타일의 메타 프로그래밍을 원한다는 결론에 도달했습니다. Lisp 스타일의 메타 프로그래밍은 코드를 데이터로 취급할 수 있다는 것입니다. 매크로는 프로그래밍 언어마다 다른 의미를 가지지만, Lisp와 Elixir, 그리고 MacLisp에서는 매크로를 호출할 때 매크로가 코드의 표현에 접근할 수 있습니다. 코드는 데이터로 표현되고 매크로는 코드를 데이터로 조작할 수 있습니다. Elixir는 Erlang 가상 머신에서 실행되기 때문에 이 방식이 Elixir에 적합하다고 생각했습니다. Erlang 가상 머신에서는 코드가 컴파일됩니다. 매크로를 통해 컴파일 중에 발생하는 이러한 전처리 방식은 Erlang 가상 머신과 코드를 컴파일하고 실행하는 방식에 매우 적합합니다. 그래서 "메타 프로그래밍을 원하고 Lisp 스타일의 메타 프로그래밍을 원한다."라는 결론에 도달했습니다. 그렇다면 당연히 "왜 Lisp를 사용하지 않았는가?"라는 질문이 생깁니다. 왜 Erlang 가상 머신에서 실행되는 Lisp를 만들지 않았을까요? 제가 Elixir를 시작했을 때는 이미 두 개, 어쩌면 세 개의 Lisp가 있었습니다. LFE는 Lisp Flavored Erlang으로, Erlang 창시자 중 한 명이 유지 관리하고 있습니다.
크리스 젠킨스: 그러니까 Erlang 가상 머신에서 사용할 수 있는 Lisp가 세 개였다는 말씀인가요?
José Valim: 네, 그 당시에는요.
크리스 젠킨스: 알겠습니다.
José Valim: Joxa라는 Lisp도 있었고, 나중에 Erlang 가상 머신에서 실행되는 Clojure도 나왔습니다. 사람들은 Erlang 가상 머신에서 Lisp를 시도해 왔던 거죠. 저는 "Lisp를 또 만들고 싶지는 않아. 장단점을 따져봐야지."라고 생각했습니다. 이 부분은 별도의 논의가 필요하겠지만, "Lisp를 원하는 게 아니라 다른 것을 시도해 보고 싶어."라고 생각했습니다. 제 생각은, 당시에는 참신하다고 생각했는데...
크리스 젠킨스: 알겠습니다.
José Valim: 기본적인 문법을 정의하고 몇 가지 편의 구조를 추가하려고 했습니다. 예를 들어, Lisp에는 연산자가 없습니다. 두 숫자를 더하려면 (+ 3 5)
처럼 작성해야 합니다. 먼저 더하기 기호를 쓰고 3과 5를 씁니다. Lisp에서는 모든 것이 같은 표기법으로 작성됩니다.
크리스 젠킨스: 네, 접두사 기반 문법이죠. 그래서 접두사 표기법을 사용하지만 몇 가지 허용을 하려고 했습니다. 연산자를 추가하고 이것저것 추가하려고 했습니다.
José Valim: 기본적인 문법으로 시작해서 그 위에 몇 가지 허용을 추가했습니다. 그러면서 "Lisp처럼 보이게 하고 싶지는 않아. Lisp가 아니니까 어떻게 보이게 할까?"라고 생각했습니다. Ruby를 많이 사용해 봤기 때문에 이 부분에서 Ruby와 비슷한 결정을 내렸습니다. 하지만 과장된 부분도 있다고 생각합니다. Elixir에서 do
와 end
를 사용하는 부분을 중괄호로 바꾸면 더 이상 Ruby처럼 보이지 않을 겁니다. do
와 end
를 사용해서 코드 블록을 구분하는 이유는 Erlang에서 구두점을 많이 사용했기 때문입니다. 앞서 말씀드렸듯이 Erlang은 시끄럽습니다. 구두점과 단어의 비율이 높죠.
크리스 젠킨스: 네, 좀 지저분해 보이죠. 함수형 프로그래밍 언어에서 흔히 볼 수 있는 특징입니다. 다시 말씀드리지만, 나쁘다는 뜻은 아닙니다. 그냥 그렇다는 뜻입니다.
José Valim: 네, 그렇습니다. 좋아하는 사람도 있고 싫어하는 사람도 있죠. 저는 "코드 블록을 구분하기 위해 do
와 end
를 사용해 보자."라고 생각했습니다. 그게 전부입니다. Elixir에서 do
와 end
를 모두 중괄호로 바꾸면 더 이상 Ruby처럼 보이지 않을 겁니다. 가장 Ruby처럼 보이게 만드는 부분은 do
와 end
, 그리고 선택적 괄호입니다. 선택적 괄호는 또 다른 이야기지만요. 어쨌든, 요점은 문법이 중요한 게 아니었다는 것입니다. "이런 특정 기능을 원한다. 메타 프로그래밍을 원하고 다형성을 원한다."라는 생각이었습니다. 이러한 기능을 구현하려면 여러 가지 결정을 내려야 했습니다. "Lisp를 원하지 않으니 어떻게 보이게 할까?"라고 생각했고, 그러한 결정들이 다른 문법을 낳았습니다. 하지만 문법은 수단이 아니라 결과였습니다. 목표에 도달하기 위해 문법을 선택한 것이죠. 참신한 아이디어라고 생각했던 부분이 나중에 알고 보니... Elixir에서는 문법이 AST로 직접 변환됩니다. 나중에 John McCarthy가 Lisp를 소개하는 논문을 읽어보니 그 논문에도 같은 아이디어가 있었습니다. 논문에서 McCarthy는 "Lisp를 사용할 것이다."라고 말했고, 그 Lisp는 Symbolic Lisp를 의미하는 S-Lisp라고 불렸습니다. McCarthy는 "이 표현, 즉 오늘날 대부분의 사람들이 Lisp라고 이해하는 Lisp는 너무 저수준이다. 이것을 변환할 수 있는 고수준 표현이 필요하다."라고 말했습니다. 그 고수준 표현은 M-Lisp라고 불렸습니다. 나중에 "아니, 처음부터, Lisp가 존재했을 때부터 있었던 아이디어잖아."라고 생각했습니다.
크리스 젠킨스: 네, 어렴풋이 기억납니다. 어디선가 Lisp가 항상 다른 표면 문법을 가지도록 의도되었지만 실제로 구현되지는 않았다는 글을 읽은 기억이 납니다.
José Valim: 네, 맞습니다. 모두가 S-Lisp를 사용했고 M-Lisp를 사용한 사람은 아무도 없었습니다.
크리스 젠킨스: 그렇다면 메타 프로그래밍, 즉 Lisp 스타일의 메타 프로그래밍은 어떻게 달라질까요? Lisp를 사용하면 작성하는 코드와 메타 프로그래밍을 할 때 얻는 데이터 구조 사이에 거의 차이가 없습니다. 거의 동일하지만 변환 과정이 필요합니다.
José Valim: 네, "이 코드는 이렇게 표현될 것이다."라는 것을 배우게 됩니다. 즉, 이 코드는 이 특정 데이터 구조가 될 것입니다. 변환 계층이 존재하는 것이죠. 솔직히 말해서 대부분의 프로그래밍 언어가 그렇게 작동합니다. 대부분의 프로그래밍 언어는 문법을 가지고 있고 문법은 표현이 됩니다. Elixir의 과제는, Erlang도 마찬가지로 작동하는데, 문법을 최소한으로 유지하는 것이었습니다. Elixir는 문법을 가지고 있고 표현을 가지고 있습니다. Elixir의 과제는 필요한 최소한의 언어가 무엇인지 결정하는 것이었습니다. 문법 변환은 간단합니다. 개발자에게 20가지 다른 변환 규칙을 배우라고 요구하지 않습니다. 그것이 바로 절충안이었습니다. 표면을 최대한 작게 유지해야 했습니다.
크리스 젠킨스: 네, 알겠습니다. 그 규칙 중 하나는 연산자를 사용하는 중위 표기법을 트리 형태로 변경하는 것이겠죠.
José Valim: 네, 맞습니다.
크리스 젠킨스: 연산자와 함수 호출은 문법적으로는 연산자가 가운데에 있고 함수 호출은 인수를 가지고 있기 때문에 다릅니다. 하지만 AST에서는 같은 방식으로 표현됩니다. 모두 호출로 간주됩니다. 예를 들어, Elixir에서는 연산자가 다른 함수 호출과 마찬가지로 함수 호출입니다. 다른 프로그래밍 언어에서는 연산자와 함수 호출을 구분합니다. 두 가지는 서로 다른 것입니다. Elixir에서는 둘 다 같은 AST로 변환됩니다. 문법적으로만 다를 뿐입니다.
크리스 젠킨스: 네, 알겠습니다. 그런 구분은 문법 수준에서만 존재하는 것 같습니다. 실제로 무엇을 달성하려고 하는지 살펴보면 그런 구분은 사라지죠.
José Valim: 네, 그렇습니다. 문법과 의미가 서로 분리되어 있다는 점은 매우 흥미로운 결과를 가져옵니다. Lisp에서도 마찬가지입니다. 문법과 의미가 완전히 분리된 것은 아닙니다. 물론 어느 정도 수준에서는 서로 연결되어 있어야 합니다. 하지만 분리되어 있다는 점은 훌륭한 기능을 제공합니다. 예를 들어, 오늘날 Nx(Numerical Elixir)라는 라이브러리가 있습니다. Nx는 Elixir를 GPU에서 실행되도록 컴파일합니다. Nx는 라이브러리일 뿐이고 언어 변경이 필요하지 않습니다. Elixir의 기존 기능을 사용해서 구현된 라이브러리입니다. 반면에 몇 가지 복잡한 문제도 발생합니다. 예를 들어, 파서에서 잘못된 문법을 발견했다고 가정해 보겠습니다. 기억나는 예는 없지만, 잘못된 문법을 발견했다고 가정해 보겠습니다. 그러면 사용자에게 "이렇게 하고 싶었던 건가요?"라고 알려주고 싶을 겁니다. 하지만 문법과 의미가 서로 분리되어 있기 때문에 문법에서 사용자가 특정 작업을 하려고 한다고 가정할 수 없습니다. "이것은 문법이고 사용자는 이것을 가져다가 GPU로 컴파일할 수도 있고 SQL 쿼리로 컴파일할 수도 있다."라고 가정해야 합니다. 따라서 문법에 대해서만 가정할 수 있고 그 이상은 가정할 수 없습니다.
크리스 젠킨스: 네, 이 부분은 다형성과도 관련이 있을 것 같습니다. 특히 동적 타입 언어에서는 매크로를 작성할 때 임의의 문법 트리를 처리해야 합니다. 그러면 "이것이 함수라면 이런 작업을 수행할 것이다."라고 말해야 합니다. 본질적으로 다형성을 띠게 되는 것이죠. 여러 가지 다른 것을 담고 있는 문법 트리를 따라 내려가야 하기 때문입니다. Elixir 스타일의 다형성은 어떤 모습일까요?
José Valim: 네, 좋은 질문입니다. 여기서 두 가지를 말씀드리고 싶습니다. 첫째, 제가 말씀드릴 내용과 Elixir의 많은 기능은 실제로 Erlang에서 유래했습니다. Erlang은 Elixir와 마찬가지로 동적 프로그래밍 언어이지만, 일반적으로 동적 프로그래밍 언어라고 생각하는 것보다 훨씬 더 단정적입니다. Ruby, Python, JavaScript와 같은 일반적인 동적 프로그래밍 언어는 보통 객체 지향적입니다. 즉, 객체에서 무언가를 호출합니다. 객체를 가지고 있고 toString
이나 toString
을 호출합니다. 그 객체가 무엇인지 꼭 알 필요는 없습니다. 덕 타이핑처럼 "이 객체가 무엇인지는 신경 쓰지 않고 toString
함수나 메서드를 구현하기만 하면 된다."라고 말합니다. Elixir에는 Erlang과 마찬가지로 객체가 없고 몇 가지 미리 정의된 데이터 유형이 있습니다. 그리고 우리는 종종 그 데이터 유형에 대해 패턴 매칭을 수행합니다. 예를 들어, Elixir는 주요 AST 노드를 세 개의 요소(함수 이름, 메타데이터, 함수에 대한 인수)를 가진 튜플로 표현합니다. 함수 호출을 세 개의 요소를 가진 튜플로 표현하는 것입니다. 그런 다음 이 튜플에 대해 패턴 매칭을 수행할 수 있습니다. 튜플의 문법을 그대로 작성하고 각 요소에 대해 패턴 매칭을 수행할 수 있습니다. 함수형 프로그래머에게는 매우 익숙한 개념입니다. 추상 데이터 유형을 생각해 보면, 추상 데이터 유형을 정의할 때 각 유형에 대해 정확하게 매칭해야 합니다. Erlang과 Elixir에서도 거의 같은 방식으로 작동합니다. 단, 존재하는 모든 형태에 대해 매칭할 수 있다는 점이 다릅니다. 튜플, 리스트, 딕셔너리 등 거의 모든 것에 대해 패턴 매칭을 수행할 수 있습니다. 이 기능은 Erlang에서 유래했습니다. 이러한 다형성을 가지고 있지만, 제가 Erlang을 처음 사용하기 시작했을 때 이 다형성에 문제가 있다고 느꼈습니다. 다른 함수형 프로그래밍 언어와 마찬가지로, 예를 들어, 다른 함수형 프로그래밍 언어를 선택해 보겠습니다. 이 문제를 어떻게 해결하는지 설명해 드리겠습니다. 이것은 분명히 다형성의 한 형태입니다. "이 함수는 여러 유형의 매개변수를 받는다."라고 말할 수 있습니다. 그런 다음 해당 유형에 대해 패턴 매칭을 수행할 수 있습니다. 정적으로 타입이 지정된 함수형 프로그래밍 언어를 사용한다면 AST에 대한 유형을 정의할 것입니다. 그런 다음 AST의 각 노드에 대해 모든 노드를 개별적으로 설명할 것입니다. 일반적인 유형을 정의하고 해당 유형의 일부로 가능한 각 노드를 정의하는 것입니다. 이 코드의 문제는 AST에 대해서는 발생하지 않는다는 것입니다. AST에 새 노드를 추가하려면 언어에 새 AST 노드를 추가해야 합니다. 하지만 예를 들어, JSON을 인코딩으로 사용한다고 가정해 보겠습니다. 웹 애플리케이션을 구축하고 있고 비즈니스 도메인 유형이 있다고 가정해 보겠습니다. 예를 들어, 전자 상거래라면 장바구니를 나타내는 유형, 쇼핑 항목을 나타내는 유형 등 비즈니스 도메인에서 모델링하는 모든 것에 대한 유형이 있을 것입니다. 이제 API에서 이러한 유형을 노출하려면 모든 유형을 JSON으로 변환해야 합니다. 이를 위해 애플리케이션에서 toJSON
이라는 함수를 만들어 애플리케이션이 알고 있는 모든 유형을 받을 가능성이 높습니다. 바로 이것이 우리가 이야기하는 다형성입니다. 매개변수 다형성이라고 할 수 있습니다. 유형이 가능한 인수로 제공되기 때문입니다. 따라서 몇 가지 래퍼가 필요할 수도 있고 필요하지 않을 수도 있지만, 시스템의 모든 유형을 받는 이 거대한 toJSON
함수를 정의해야 합니다. 매개변수로 받아서 변환하는 것입니다. 이것이 가능한 방법 중 하나입니다. 새로운 유형이 생길 때마다 누군가가 가서, 예를 들어, 제 팀의 다른 구성원이 새로운 유형을 추가하면 toJSON
함수를 변경해야 합니다. 애플리케이션에서는 문제가 없지만 생태계에서는 문제가 될 수 있습니다. 이제 제가 이 애플리케이션을 작업하고 있고 돈 유형을 도입하는 라이브러리를 사용해야 한다고 가정해 보겠습니다. 부동 소수점으로 돈을 모델링하고 싶지는 않을 겁니다. 통화, 돈 등을 모델링하는 특정 유형을 원할 것입니다. 하지만 이제 라이브러리를 애플리케이션에 가져오면 거대한 toJSON
함수에서 해당 유형도 처리해야 합니다. 거기서 처리해야 합니다. 하지만 표준 라이브러리에 집합 데이터 유형이 없다고 가정해 보겠습니다. 다른 라이브러리를 가져오면 집합 라이브러리 안에 돈 유형이 있을 수 있습니다. 이제 가서... 구성하려면, 즉 "이 집합을 가져와서 JSON을 처리하는 방법을 제공하고 모든 사람이 이러한 것을 함께 구성할 수 있도록 하여 JSON을 렌더링할 수 있도록 하겠다."라고 말하려면 이러한 다형성이 필요합니다. Erlang은 항상 동적이었고 이러한 매개변수 다형성을 가지고 있었지만 확장 가능하지 않았습니다. 함수에 제 데이터 유형을 처리하는 방법을 가르치려면 코드를 포크하거나 변경해야 했습니다. 가서 변경해야 했습니다. 이러한 제약은 계약 생성을 제한합니다. "JSON으로 변환 가능한 모든 데이터 유형을 제공하면 작동합니다."라고 말하는 라이브러리를 가질 수 없습니다. 이러한 확장 가능한 메커니즘이 없기 때문입니다. 모든 사람이 "이것을 구현하겠습니다."라고 말할 수 있는 메커니즘이 없습니다. 이러한 계약을 정의할 수 없습니다. 매개변수 다형성은 있지만 한곳에만 있습니다. 해당 항목에 원하는 만큼 절을 추가할 수 있지만 변경해야 합니다. Elixir에 추가한 기능은 오늘날 매우 인기 있는 기능입니다. Go 인터페이스, Haskell의 타입 클래스, Clojure의 프로토콜과 동일합니다. 확장 가능한 다형성입니다. "이 작업을 수행할 수 있는 모든 데이터 구조를 제공하면 나머지는 알아서 처리하겠습니다."라고 말할 수 있습니다. 이러한 다형성은 어디에나 사용됩니다. 예를 들어, 프로그래밍 언어에 REPL, 터미널이 있다고 가정해 보겠습니다. 터미널에 출력하기 위해 데이터 구조를 변환하는 방법은 어떻게 알 수 있을까요? 데이터 구조를 가지고 있고 터미널에 출력하고 싶다면 모든 것을 하드코딩할 수 있습니다. 하지만 프로토콜을 사용하는 것이 더 좋습니다. "이 데이터 구조를 어떻게 표현하고 싶은지 알려주세요."라고 말할 수 있습니다. 그러면 내부 데이터 유형의 고수준 표현을 제공할 수 있습니다. 이렇게 두 가지를 모두 살펴보았습니다. Elixir에 추가하고 싶었던 두 가지는 바로 메타 프로그래밍과 이러한 확장 가능한 다형성이었습니다. 구현 방법은 여러 가지가 있지만 Elixir에서는 프로토콜이라고 부릅니다. 프로토콜은 "이것을 구현하기만 하면 무엇이든 제공할 수 있습니다. 알아서 처리하겠습니다."라고 말할 수 있는 다형성을 제공합니다.
크리스 젠킨스: 알겠습니다. 누군가가 터미널에 show
와 같은 프로토콜을 작성할 수 있고, 데이터 구조를 작성하는 사람이 구현할 수도 있고 프로토콜을 작성하는 사람이 구현할 수도 있습니다. 관계의 특정 측면에 얽매이지 않습니다.
José Valim: 네, 맞습니다. Rust의 트레잇과도 같습니다. 요약하자면, 처음에 "Erlang을 사용하기 시작했을 때 몇 가지 부족한 점이 있다고 느꼈다."라고 말씀드렸습니다. "객체가 부족하다고 생각했지만 실제로는 객체가 아니라 이 특정 형태의 다형성이 부족했던 것입니다." Java에는 인터페이스가 있지만, 문제는 객체가 인터페이스를 구현해야 한다는 것입니다. 새로운 인터페이스를 사용하려면 객체 소스 코드에 액세스할 수 있어야 합니다.
크리스 젠킨스: 100% 확신할 수는 없지만, 다른 객체에 대한 인터페이스를 구현할 수 있어야 한다고 생각합니다. 하지만 문제는, Elixir에서도 마찬가지지만, 소유하지 않은 객체에 대한 인터페이스를 구현하는 경우 해당 객체가 인터페이스를 구현하는 데 필요한 기능을 노출해야 한다는 것입니다. 그렇지 않으면 인터페이스를 구현할 수 없습니다. 무슨 말씀인지 아시겠죠?
크리스 젠킨스: 네, Java에서는 객체에 액세스할 수 없는 경우 소유한 인터페이스를 구현하기 위해 존재하는 래퍼 객체를 만들어야 합니다. 래퍼를 소유해야 인터페이스를 추가할 수 있습니다. 하지만 프로토콜과 같은 것이 있다면 그럴 필요가 없다는 말씀이시죠. 어느 쪽을 소유하든 상관없습니다.
José Valim: 네, 맞습니다. 하지만 여전히 데이터 구조나 데이터 구조를 사용하는 모듈이 각 인터페이스를 구현하는 데 충분한 정보를 제공해야 합니다. 데이터 유형의 내부에 액세스해서는 안 됩니다. 여전히 몇 가지 경계가 있지만 언어적 제약은 아닙니다. 소프트웨어 디자인의 제약입니다.
크리스 젠킨스: 네, 알겠습니다. Clojure가 Elixir에 영향을 미쳤다는 것을 알고 있습니다. Clojure에는 멀티 메서드라는 것이 있습니다. 다형성 디스패치를 수행할 수 있지만 논리를 포함할 수도 있습니다. 예를 들어, 첫 번째 인수가 5보다 작으면 실행할 메서드가 됩니다. 그렇지 않으면 다른 곳을 찾습니다. Elixir에도 그런 기능이 있나요?
José Valim: 네, 있습니다. toJSON
함수가 작동하는 방식은 Clojure의 멀티 메서드와 매우 유사합니다. 하지만 확장 가능하지 않다는 점이 다릅니다. 한 번만 정의합니다. 디스패치 규칙을 만들 수 있습니다. 예를 들어, 이 인수가 이것이고 다른 인수가 저것이라면... 우리는 이것을 패턴과 가드라고 부릅니다. 그런 식으로 코드를 작성할 수 있지만 확장 가능하지는 않습니다. Clojure의 멀티 메서드는 확장 가능하다고 생각합니다. 언제든지 멀티 메서드에 새 규칙을 추가할 수 있습니다. 정의나 객체를 소유할 필요가 없습니다. 제3자가 될 수 있습니다. 거의 몽키 패칭과 비슷합니다.
크리스 젠킨스: 네, 하지만 네임스페이스 충돌이라는 큰 문제가 있습니다. 몽키 패칭은 멀티 메서드가 가지고 있지 않은 문제를 일으킬 수 있습니다. Julia라는 언어도 우리가 설명하는 기능을 가지고 있습니다. Julia에서는 모든 것이 멀티 메서드라고 할 수 있습니다. 함수가 있으면 언제든지 모든 유형에 대해 디스패치하는 새 절을 정의할 수 있습니다. 매우 흥미로운 기능입니다. Clojure는 Elixir에 영향을 미쳤습니다. 우리가 다형성 프로토콜을 프로토콜이라고 부르는 이유는 Clojure 때문입니다. Elixir의 디자인이 Clojure와 가장 가깝기 때문입니다. 멀티 메서드는 프로토콜보다 더 일반적입니다. 이상적인 세계에서는 멀티 메서드를 선호합니다. 멀티 메서드가 더 일반적이기 때문에 더 많은 것을 표현할 수 있습니다. Julia와 같이 멀티 메서드를 사용해서 흥미로운 작업을 수행하는 프로그래밍 언어도 있습니다. 지금도 그런지는 모르겠지만, 예전에 Clojure 개발자들과 이야기를 나누었을 때 멀티 메서드는 거의 사용되지 않고 어떤 이유로 프로토콜이 선호된다는 이야기를 들었습니다. 하지만 가장 큰 문제는 Erlang 가상 머신에서 멀티 메서드를 효율적으로 구현할 수 없다는 것이었습니다. 어쩌면 가능할 수도 있지만 쉽지는 않습니다. 지금은 10년, 12년의 경험이 쌓였으니 "다시 돌아가서 멀티 메서드를 설계해 볼 수 있을까?"라고 생각할 수도 있지만, Clojure도 해결하지 못한 문제가 있습니다. 한 가지 작업을 수행하지만 많은 작업을 수행하지 않는 기능이 있을 수 있지만, 매우 유연한 기능이 있을 수도 있습니다. 그러면 사람들이 많은 것을 사용할 수 있고, 그러면 코드를 이해하거나 유지 관리하기가 더 어려워질 수 있습니다. 멀티 메서드가 다른 메서드를 덮어쓰지 않도록 하려면 어떻게 해야 할까요? 누구든지 언제든지 멀티 메서드를 등록할 수 있다면 충돌이 발생하지 않도록 하려면 어떻게 해야 할까요? 이러한 문제를 해결하고, 멀티 메서드를 보호할 수 있는 방법을 허용하면, 예를 들어, 사람들이 임의의 표현식이나 비교 연산자를 작성할 수 있도록 허용하면, x가 5보다 크면 "사용자가 작성한 내용을 덮어쓰고 있습니다."라고 말하는 코드를 어떻게 구현할 수 있을까요? 이는 디버깅하기 매우 어려운 문제로 이어질 수 있습니다. 라이브러리 C가 라이브러리 B가 작동할 것으로 예상했던 내용을 정의하고, 이제는 매우 멀리 떨어져 있는 라이브러리가 예상대로 작동하지 않는 경우가 발생할 수 있습니다. 이러한 절충안이 존재하고, 더 일반적인 메커니즘을 갖는 것이 강력한 기능이 될 수 있다고 생각합니다. 하지만 그런 기능을 추가하려면 사용자가 명백한 실수나 디버깅하기 어려운 실수를 하지 않도록 균형을 맞추고 몇 가지 검사를 추가해야 합니다.
크리스 젠킨스: 네, 완전히 이해합니다. 그런 속성을 보장하는 것은 어려운 일입니다. 모든 기능을 열어놓고 다시 닫을 수 있는 유용하고 사용하기 쉬운 방법을 설계해야 합니다. 언어 설계자들이 흥미로운 점은 바로 이러한 힘의 스펙트럼에서 어디에 위치하느냐는 것입니다. 제가 전문 Clojure 프로그래머였던 지 꽤 오래되었지만, 제가 Clojure를 사용했을 때는 멀티 메서드가 거의 사용되지 않았습니다. 멀티 메서드는 매우 강력해서 무엇이든 할 수 있지만, 다른 곳에 정의가 있는지 몰랐기 때문에 실제 동작이 마법처럼 보일 위험도 있습니다. 힘과 사용성 사이의 적절한 지점을 찾는 것이 중요합니다.
José Valim: 네, 맞습니다. 정답은 없다고 생각합니다. 가끔 "이 부분을 다르게 했다면 어땠을까?"라고 생각하기도 합니다. 그러면 다른 많은 것들을 포기해야 할 수도 있고, "그 선택은 하지 않을 것이다."라고 생각하게 될 수도 있습니다. 저는 Lisp 스타일의 메타 프로그래밍을 그대로 유지할 것입니다. Lisp 스타일의 메타 프로그래밍은 문법과 의미가 서로 분리되어 있다는 좋은 예입니다. 예를 들어, 언어 서버를 구현하는 경우 Elixir 코드의 99.9%에서 더하기 기호는 더하기를 의미하지만 누군가가 가서 더하기 기호를 다른 의미로 바꿀 수도 있습니다. 그러면 도구를 만들기가 더 어려워집니다. 균형에 대해 이야기하고 있으니 Elixir에서 어떻게 했는지 설명해 드리겠습니다. Elixir에서는 더하기 기호를 바꿀 수 있지만 모든 것이 어휘적입니다. 더하기 기호의 의미를 바꾸려면 코드에 "더하기 기호를 바꾸고 있습니다."라고 명시해야 합니다. 전역적으로 작업을 수행할 수 없습니다. Elixir에서 더하기 기호의 의미를 전역적으로 변경할 수 없습니다. Elixir에서는 더하기 기호가 함수이기 때문에 "커널에서 더하기 기호를 가져오고 싶지 않습니다. 이 다른 더하기 기호를 가져오고 싶습니다."라고 말할 수 있습니다. 코드에 명시되어 있기 때문에 언어 서버는 코드를 살펴보고 이러한 지시문을 처리해야 합니다. "이것을 제거하고 이것을 추가하고 싶습니다."라고 말하는 것입니다. 더 많은 작업이 필요합니다. "이러한 기능을 제공하지 않을 수도 있었다."라고 말할 수도 있습니다. 그러면 언어 서버 쪽에서 작업이 줄어들 것입니다. 하지만 더하기 기호는 매우 작은 기능입니다. 더하기 연산자일 뿐입니다.
크리스 젠킨스: 네.
José Valim: GPU에서 실행되는 Elixir 코드를 작성할 수 있게 해주는 기능입니다. 더하기, 나누기, 곱하기를 사용하는 Elixir 코드를 작성할 수 있습니다. Nx는 더하기 기호를 바꿉니다. "Numerical Elixir 연산을 GPU Elixir 연산으로 바꾸고 싶습니다."라고 명시적으로 호출합니다. 매우 구체적입니다.
크리스 젠킨스: 네, 알겠습니다.
José Valim: Nx는 더하기 기호를 바꾸고, 저에게는 놀라운 기능입니다. 매우 흥미로운 기능입니다. "정말 어려운 절충안이군."이라고 생각했습니다. 저에게는 이러한 견제와 균형이 중요합니다. "프로그래밍 언어로 표현력을 높이고 싶지만 누군가가 의미를 바꾸는 것을 허용하고 싶지는 않습니다." 몽키 패칭과 같은 것입니다. "코드를 작성했는데 해당 코드를 로드하거나 종속성을 추가하기만 하면 더하기 기호의 의미가 바뀌는 것을 원하지 않습니다." 하지만 "바꾸고 싶습니다. 바꿀 수 있어야 합니다."라고 말할 수도 있습니다.
크리스 젠킨스: 네, 메타 프로그래밍의 문을 열면 전역 변수와 지역 변수의 문제가 다시 불거집니다. 전역 변수에서 더하기 기호는 무엇을 의미할까요?
**
========== translation continues ... ===========
José Valim: 네, 그렇습니다. 그래서 Elixir에서는 모든 매크로를 명시적으로 요구해야 합니다. 매크로를 호출하려면 먼저 매크로를 요구해야 합니다. Elixir에서는 Clojure와 마찬가지로 다른 모듈을 호출할 때 명시적으로 요구할 필요가 없습니다. 가져오거나 그런 작업을 할 필요가 없습니다. 많은 언어에서는 그런 작업을 요구합니다. 편리하죠. 명시적 가져오기를 사용하는 프로젝트에서 작업할 때는 코드가 필요하지 않습니다. 처음 20줄은 가져오기 작업으로 가득 차 있습니다. Elixir에서는 그럴 필요가 없습니다. 하지만 매크로의 경우에는 이러한 요구 사항을 유지했습니다. "코드를 잠재적으로 변경할 수 있는 것을 가져오고 있습니다."와 같은 신호를 보내는 것입니다.
크리스 젠킨스: 네, 게임의 규칙을 바꾸려면 명시적으로 해야 합니다. Rust도 비슷한 방식을 사용합니다. 모든 매크로는 느낌표로 끝납니다. "예상과 다르게 작동할 수 있습니다."라는 경고를 보내는 것입니다.
José Valim: 항상 명시적입니다. 옵션은 있지만 "이것을 원합니다."라고 말해야 합니다.
크리스 젠킨스: 네, 알겠습니다. 잠시 후에 Numerical Elixir에 대해 자세히 이야기해 보겠습니다. 하지만 이 전체 "컴파일 시간에 무엇을 할까?"라는 질문과 관련된 핵심 주제가 하나 더 있습니다. 최적화, 컴파일과 관련된 주제입니다. 우선, Elixir는 Erlang으로 컴파일되나요? 아니면 Erlang이 컴파일되는 것과 같은 바이트코드로 컴파일되나요?
José Valim: Elixir는 Elixir AST를 가져와서 Erlang AST로 변환합니다. 대부분의 컴파일러는 여러 표현과 여러 단계를 거칩니다.
크리스 젠킨스: 네, 고수준 AST로 시작해서 어느 시점에 최적화를 수행하기에 더 적합한 다른 것으로 변환됩니다. 네, 그리고 마지막에는 바이트코드가 생성됩니다. Erlang에서는 Erlang 가상 머신을 대상으로 하는 경우 Erlang AST, Core Erlang AST, 바이트코드의 세 가지 옵션이 있습니다.
José Valim: 저는 Erlang AST를 선택했습니다. 실제로 Erlang 소스 코드로 컴파일할 수도 있습니다. Gleam이 그렇게 합니다. Gleam은 AST가 아니라 텍스트 Erlang 파일로 컴파일합니다. 하지만 이 방법에는 몇 가지 큰 제약이 있습니다. 예를 들어, 스택 추적에서 줄 번호가 맞지 않습니다. Gleam에서 예외가 발생하면 스택 추적이 실제 코드와 일치하지 않습니다.
크리스 젠킨스: 네, JavaScript 커뮤니티에서는 소스 맵과 같은 방법으로 이러한 문제를 해결해야 했습니다.
José Valim: 네, 그래서 네 가지 옵션이 있다고 생각합니다. 저희는 적절한 스택 추적과 메시지를 얻을 수 있으면서도 Erlang과 최대한 많은 도구를 공유할 수 있기 때문에 Erlang AST를 선택했습니다. 처음에 그렇게 한 이유는 Erlang에 Cover라는 테스트 커버리지 도구가 있기 때문입니다. 테스트 커버리지는 지금은 바뀌었을 수도 있지만, 적어도 그 당시에는 Erlang AST를 분석하고 테스트 커버리지에 대한 주석을 추가했습니다. Erlang AST를 대상으로 하지 않으면 Cover를 직접 구현해야 했습니다.
크리스 젠킨스: 네, Dialyzer라는 불일치 분석 도구도 있습니다. 타입 시스템은 아니지만 Erlang 및 Elixir 코드에서 버그를 찾는 것을 목표로 합니다. Dialyzer도 처음에는 Erlang AST가 필요했습니다.
José Valim: 네, Erlang에서 사용할 수 있는 도구를 최대한 활용하기 위해 Erlang AST를 선택했습니다. 하지만 나중에 시간이 지나면서 Core Erlang으로 컴파일하는 옵션도 갖고 싶었습니다. 그러면 지금은 할 수 없는 일들을 할 수 있을 것입니다. 더 많은 표현력을 얻을 수 있을 것입니다. 시간이 지나면서 Erlang 컴파일러 도구에 많은 개선 사항을 적용했습니다. 이제 프로그래밍 언어는 "자체 표현을 유지하겠습니다. 다른 것이 필요하면 다른 것을 제공하겠습니다."라고 말할 수 있습니다. 프로그래밍 언어와 도구 사이의 계약을 만들었습니다. 이제 선택할 수 있는 유연성을 갖게 되었지만 실제로 변경하지는 않았습니다. 여전히 Erlang AST를 대상으로 하지만 나중에 더 낮은 수준으로 이동하고 싶다면 그렇게 할 수도 있습니다.
크리스 젠킨스: 알겠습니다. Elixir 매크로에서 말씀하셨듯이, Elixir 문법이 AST로 어떻게 변환되는지에 대한 직관을 가지고 있다면 매크로를 작성할 수 있다는 것이 큰 장점이라고 생각합니다. Erlang 소스 코드가 어떤 AST를 나타내는지 알고 있기 때문에 어떻게 컴파일해야 하는지 알 수 있습니다.
José Valim: 네, 변환 계층은 매우 간단합니다. 솔직히 말해서 전체 변환 계층에서 유일하게 까다로운 부분은 Erlang과 Elixir가 변수를 처리하는 방식의 차이입니다. 두 언어의 가장 큰 차이점입니다. Erlang에서는 변수를 한 번만 할당할 수 있습니다. x = 1
을 수행하면 나중에 x = 2
를 수행할 수 없습니다. 장단점을 비교하는 긴 블로그 게시물을 작성했습니다. Elixir는 재바인딩을 허용합니다. 재바인딩을 허용하는 이유 중 하나는 매크로 때문이지만, 이 부분은 별도의 주제입니다. 매크로는 같은 변수를 사용해서 정보를 전달하고 싶어할 수 있습니다.
크리스 젠킨스: 네.
José Valim: 재바인딩해야 하고 매번 새 이름을 생성할 수는 없습니다. 그러면 새 이름을 추적할 위치를 찾아야 하기 때문입니다. 몇 가지 복잡한 문제가 있지만, 그것은 이유 중 하나였습니다. 또 다른 이유는 Erlang 코드에서 많은 곳에서 그런 패턴을 보았기 때문입니다. Erlang에서는 모든 것이 불변입니다. 따라서 모든 것을 변환해야 합니다. 때로는 변환하지만 궁극적으로는 같은 것입니다. Erlang 코드에서는 post0
, post1
, post2
, post3
와 같은 변수를 볼 수 있습니다. 변수를 재할당할 수 없기 때문에 버전을 지정해야 합니다. 이것이 두 언어의 큰 차이점입니다. 또 다른 차이점은 Erlang의 변수 범위 지정 방식이 마음에 들지 않는다는 것입니다. case
나 if
문에서 범위 내에서 변수를 변경하면 모든 절에서 같은 변수를 할당하는 한 해당 변수가 범위 밖에서도 사용 가능할 수 있습니다. 모든 절에서 같은 변수를 할당하지 않으면 컴파일 오류가 발생합니다. 내부 범위에 있고 해당 범위에 대한 변경 사항이 부모 범위에 영향을 미칠 수 있습니다. Elixir에는 그런 기능이 없습니다. Elixir에서는 조건문 내부에서 변수를 변경하려면 조건문의 결과로 반환해야 합니다. Elixir의 case
, if
, switch
문은 모두 범위를 정의합니다. 그렇게 하는 언어는 많지 않습니다. 함수형 프로그래밍 언어는 그렇게 하는 경향이 있습니다. 리팩토링을 훨씬 쉽게 만들어주기 때문에 좋은 기능이라고 생각합니다. 코드 조각이 있고 조건문이 있고 조건문의 일부를 별도의 코드로 옮기고 싶다면 해당 코드를 가져와서 옮기고 같은 인수를 전달하기만 하면 됩니다. 하지만 외부 범위를 변경하는 변수를 설정하는 조건문이 있다면, 즉 부모 범위를 변경하는 조건문이 있다면 이제 "이 변수가 여기에 있었는데..."와 같은 모든 문제를 고려해야 합니다. 이제 변수를 다시 전달해야 합니다. 모든 작업을 수행해야 합니다. Elixir에서는 코드를 옮기고 싶다면 입력과 출력만 신경 쓰면 됩니다. 범위 변경은 없습니다.
크리스 젠킨스: 조건문과 제어 흐름이 함수와 같은 의미를 갖도록 하는 것이죠?
José Valim: 네, 맞습니다.
크리스 젠킨스: 좋습니다. Elixir의 기초에 대해 이야기하기 전에, 초기 작업은 주로 파싱과 변환에 집중되었다고 말씀하셨습니다. Elixir와 같은 프로젝트를 어렵게 만드는 것은 무엇일까요?
José Valim: 음... 초기에는... 네, 맞습니다. 초기에는... 초기가 언제인지 생각해 봐야겠지만, 저에게는 언어 디자인에 가장 집중했던 시기였습니다. 언어가 어떻게 보일지, 어떤 기능을 원하는지, 해당 기능이 Erlang 가상 머신에 어떻게 매핑되는지, 해당 기능을 어떻게 구현할 수 있는지 파악하는 것이었습니다. Elixir가 존재하는 이유는 Erlang 가상 머신 때문입니다. 따라서 기능을 최대한, 적어도 제가 아는 한 최선의 방법으로 구현하고 싶었습니다. 일반적으로 말해서 저희는 그 목표를 달성했다고 생각합니다. UTF-8에 대해 말씀드렸듯이, 오늘날 Erlang은... Elixir에서는 초창기부터 제 이름을 대문자로 쓰면 악센트가 있는 "é"도 올바르게 대문자로 변경됩니다. 유니코드 작업을 올바르게 수행합니다. 나중에 Erlang은 Elixir의 유니코드 구현을 가져와서 표준 라이브러리에 추가했습니다. Elixir에서는 메타 프로그래밍을 사용할 수 있기 때문에 Erlang과는 다르게 구현했습니다. 하지만 아이디어는 같습니다. 처음에는 "이러한 속성을 갖고 싶다."라는 생각이었습니다. 메타 프로그래밍을 갖고 싶었습니다. 메타 프로그래밍과 다형성을 갖고 싶었던 이유는 확장 가능한 프로그래밍 언어를 만들고 싶었기 때문입니다. 확장 가능한 프로그래밍 언어는 사람들이 다양한 도메인의 문제를 해결하기 위해 사용할 수 있기 때문에 좋습니다. 웹, GPU 컴파일, 데이터 처리 등에 사용할 수 있습니다. 그것이 바로 아이디어였습니다. 그리고 이러한 것들을 파악하는 것은 파싱, 문법 파악, 무엇이 작동하고 무엇이 작동하지 않는지 파악하는 것이었습니다. 하지만 이 초기 단계, 즉 처음 2년이 지난 후에는 언어가 확장 가능하도록 설계되었기 때문에 이제 대부분의 작업은 실제로 언어와 생태계에서 이루어지고 있습니다. 저는 Elixir 자체보다 Elixir 라이브러리와 프레임워크를 작업하는 데 대부분의 시간을 할애합니다. Elixir는 접근하기 쉽도록 설계되었고 병목 현상이 되도록 설계되지 않았습니다. 허락을 구할 필요가 없습니다. 언어를 확장하고 다른 곳으로 가져가서 다양한 아이디어를 탐구할 수 있습니다. 기초가 마련된 후에는 대부분의 작업이... 저는 주로 Elixir 코드를 작성하고 있습니다. 대부분의 표준 라이브러리는 Elixir로 구현되어 있습니다. 대부분의 도구는 Elixir로 구현되어 있습니다. 그때부터 저는 주로 Elixir 코드를 작성하고 있습니다.
크리스 젠킨스: 좋은 지지ですね. 사용하고 싶은 언어를 작성하는 것이 목표였고, 프로젝트를 수정하는 것보다 실제로 사용하는 데 비교적 빨리 옮겨갔다는 것은 좋은 신호입니다. 언어 디자인의 토끼굴에 빠지기 쉽지만, 꽤 빨리 일상적인 도구로 사용할 수 있는 수준까지 끌어올렸다는 말씀이시죠.
José Valim: 네, 그리고... 몇 가지... 프로그래밍 언어를 홍보하려고 할 때 의미 없는 논쟁이 많이 발생합니다. 누군가가 "더 밀어붙일 수 있습니다. Elixir를 Elixir 자체로 작성할 수 있습니다. 즉, 언어를 부트스트랩할 수 있습니다."라고 말하는 논쟁이 있었습니다. 하지만 저에게는 Elixir는 부트스트랩되지 않았습니다. 그러면 어떤 사람들은 "부트스트랩되지 않은 프로그래밍 언어는 사용할 가치가 없다고 생각합니다."라고 말할 것입니다. 저는 "음..." Elixir 저장소의 95% 정도가 Elixir 파일입니다. 나머지는 셸 파일, Erlang 파일 등입니다. 더 밀어붙일 수도 있었지만, 저희에게는 "언어를 접근하기 쉽게 만들고 싶습니다. 그것이 필요한 것입니다."라는 생각이 더 중요했습니다. 거기서부터 "이제 나머지를 만들 수 있습니다. 저에게는 괜찮습니다. 모든 것을 다시 작성할 필요는 없습니다. 저는 Erlang을 작성하는 것도 좋아하기 때문에 Erlang도 계속 사용하고 있습니다."
크리스 젠킨스: 네, 소스 트리로 변환할 수 있어야 할 때 Erlang을 작성하는 능력을 잃고 싶지는 않을 것입니다.
José Valim: 네.
크리스 젠킨스: 네, 알겠습니다. 그러면... 녹음 전에 5년 전에 Elixir가 기본적으로 완성되었다고 말씀하셨습니다. 하지만 여전히 Elixir를 작업하고 계시죠. 오늘날 Elixir 작업은 어디에서 이루어지고 있나요?
José Valim: 네, "Elixir는 프로그래밍 언어로서 완성되었고 이제 대부분의 작업은 커뮤니티, 생태계에서 이루어질 것입니다."라는 발표를 했습니다. 제가 이 발표를 했을 때 이미 그런 일이 일어나고 있었습니다. 사람들이 놀라지는 않았을 거라고 생각합니다. 이미 그렇게 진행되고 있었기 때문입니다. Elixir 1.6부터, 지금으로부터 7년 전쯤 된 것 같은데, 꽤 오래되었죠. Elixir를 개선할 때는... "이 방향으로 가고 싶다."라는 생각 때문이 아니었습니다. 방향은... 죄송합니다. 언어를 특정 방향으로 이끌고 싶다는 생각 때문이 아니었습니다. 언어는 이미 원하는 위치에 있었습니다. 하지만 요즘에는 사람들이 "코드 포맷터가 필요합니다. Elixir를 사용하는 팀이 점점 늘어나고 있고 의미 없는 포맷팅 논쟁을 하고 싶지 않습니다. 코드 포맷터가 필요합니다."라고 말합니다. 사람들은 "Elixir 핵심 팀이 Elixir 코드를 작성하는 방법을 알려주는 가이드를 작성해 주세요."라고 말합니다. 저는 "차라리 자동화하는 게 낫겠다."라고 생각했습니다. 모두가 "누군가가 우리에게 무엇을 해야 할지 알려주는 것을 받아들이겠습니다."라고 말한다면, "좋습니다. 그렇다면 도구를 사용해서 자동화합시다."라고 생각했습니다. 처음에는 확신하지 못했지만 지금은 완전히 확신합니다. 사람들은 "Elixir 경험을 배포해야 하고 Elixir의 배포 경험을 개선하고 싶습니다."라고 말합니다. 그래서 저희는 배포 경험을 개선하는 작업도 했습니다. "언어의 일부가 되어야 하는 것들이 있습니다. 규칙을 정의해야 합니다."라는 생각을 꽤 오랫동안 해 왔습니다. 언어의 일부가 되는 것과 그렇지 않은 것은 무엇일까요? 저희는 언어를 접근하기 쉽게 만들고 싶다는 점을 염두에 두고 규칙을 정의했습니다. 그 규칙은 해당 기능을 구현하거나 극대화할 수 있는 유일한 방법이 언어의 일부로 만드는 경우에만 언어에 추가한다는 것입니다. 언어의 일부로 만드는 것은 해당 기능에 특별한 이점을 가져다줄 것입니다. Elixir는 확장 가능하도록 설계되었습니다. 이 규칙에 맞는 것은 많지 않습니다. 예를 들어, Elixir에는 JSON 라이브러리가 없습니다. JSON 패키지를 추가하기만 하면 되기 때문입니다. 패키지를 가져오는 줄을 추가하는 것과 Elixir에서 패키지를 사용하는 것은 같습니다. 사용자 경험은 100% 동일합니다. 차이점은 종속성을 위해 추가하는 한 줄의 코드뿐입니다. 대부분의 경우 이러한 방식으로 접근했습니다. 하지만 포맷터와 같은 것은 언어에서 "이렇게 작동해야 합니다."라고 말하는 것이 합리적입니다.
크리스 젠킨스: 네.
José Valim: 목표가 합의를 이루고, 즉 모든 사람이 코드를 같은 방식으로 포맷팅하는 것이라면 언어에서 코드를 포맷팅하는 것이 좋습니다. 언어에 추가하는 다른 것들은 Erlang에 이미 존재하는 것들입니다. Erlang에 이미 존재하기 때문에 Elixir에 노출하는 것이 합리적일 수 있습니다. 이미 어려운 작업이 완료되었습니다. 따라서 Elixir에 노출하는 것이 합리적입니다. 대부분의 경우에는 합리적이지 않다고 생각합니다. Erlang 버전을 직접 사용하면 됩니다. 하지만 어떤 경우에는 합리적입니다. 배포에 대해 이야기할 때 Erlang에는 릴리스라는 기능이 있습니다. 릴리스는 애플리케이션, 런타임 등 모든 것을 프로덕션 환경으로 배포할 수 있는 단일 디렉터리에 넣습니다. "Erlang에는 이미 그런 기능이 있으니 그 위에 구축해서 같은 경험을 제공합시다."라고 생각했습니다. JSON에 대해 이야기했었죠. Erlang은 한 달 전에 JSON을 추가했습니다. 이제 Elixir에서 프로토콜을 사용해서 JSON을 노출할 것입니다. 이제 Elixir에서도 JSON을 사용할 수 있지만, 그 동기는... Erlang에 있기 때문에 Elixir에도 있는 것이 합리적이라는 것입니다. Erlang에 JSON이 있는 이유는 Erlang이 통신에 뿌리를 두고 있기 때문에 통신에 사용되는 프로토콜 중 하나가 JSON 인코딩을 추가했기 때문입니다. 매우 오래된 표준인 ASN에 JSON 지원이 추가되었습니다. Erlang은 "JSON 지원이 필요합니다. 5G나 6G에서 JSON을 사용하고 있기 때문입니다."라고 말했습니다. 이제 Erlang에 JSON이 추가되었고, 저희는 "좋습니다. 이제 Elixir에도 JSON을 추가합시다."라고 생각했습니다. 하지만 언어의 일부가 되어야 하는 것에 대한 규칙을 정의할 수 있었습니다. "우리가 해결해야 할 대부분의 문제를 해결했고 언어의 일부가 되는 것이 합리적입니다."라고 생각했습니다. 따라서 언어는 거의 완성되었습니다. 대부분의 작업은 생태계에서 이루어져야 합니다. 5년 전에는 그렇게 생각했습니다.
크리스 젠킨스: 네.
José Valim: 50%는 맞고 50%는 틀렸습니다.
(웃음)
José Valim: Elixir 커뮤니티에서 일어난 흥미로운 일들은 대부분 커뮤니티에서 일어났기 때문에 50%는 맞았습니다. 머신 러닝, Elixir를 GPU로 컴파일, LiveView, 오디오 및 비디오 스트리밍 등 많은 일들이 일어났습니다. 이러한 일들은 모두 커뮤니티에서 일어났습니다. 저희는 언어를 변경해서 이러한 일들이 일어나도록 하지 않았습니다. 이러한 일들은 모두 Elixir 주변에서 일어났고, 그것은 환상적인 일입니다. 하지만 50%는 틀렸습니다. 그 이유는... 그 발표를 한 지 1년, 1년 반쯤 지났을 때... 사람들이 항상 저에게 말했던 것 중 하나는... 항상은 아니지만... Elixir의 문제점을 해결하면서 누군가에게 "Elixir에서 무엇을 개선해야 할까요?"라고 물으면 많은 사람들이 "타이핑이 필요합니다."라고 대답했습니다. 정말 많은 사람들이 그렇게 말했습니다. Elixir 행사에 가서 "Elixir에 타이핑 기능을 더 추가하고 싶은 사람이 있나요?"라고 물으면... 정적 타입 시스템을 구체적으로 언급하지는 않았지만 Elixir가 타입 방향으로 더 나아가서 개발자를 위한 더 나은 도구를 제공하기를 원하는지 물었습니다. 50% 이상의 사람들이 손을 들었습니다.
크리스 젠킨스: 놀랍네요. 타이핑은 언어를 선택할 때 결정하는 큰 기준 중 하나이기 때문입니다. 많은 사람들이 언어를 선택할 때 이미 어느 쪽을 선호하는지 결정합니다.
José Valim: 네, 맞습니다. 좋은 지적입니다. Elixir에서 타이핑에 대한 요구가 그렇게 큰 이유는 몇 가지가 있다고 생각합니다. Elixir 코드는 이미 단정적이라고 말씀드렸습니다. toString
을 호출하는 객체가 없습니다. 프로토콜이 있지만 프로토콜은 명시적으로 정의된 계약입니다. 여러 절이 있는 JSON과 같은 다른 다형성이 있는 경우 받는 항목의 유형을 명시적으로 지정합니다. "사용자를 받을 것입니다. 고객을 받을 것입니다."라고 말합니다.
크리스 젠킨스: 타입은 없지만 항상 타입에 대해 매칭하고 있습니다.
José Valim: 네, 맞습니다. 타입 개념은 분명히 존재합니다. 검사가 수행되는 방식이 다를 뿐입니다. 코드가 너무 단정적이기 때문에 Dialyzer와 같은 도구가... Dialyzer라는 도구가 있습니다. Dialyzer는 불일치 분석 도구입니다. Dialyzer는 코드를 살펴보고 실패할 수 있는 부분을 찾습니다.
크리스 젠킨스: 네, 도구가... 이 도구는 주석을 사용하지 않고 코드만 살펴봅니다. 코드를 살펴보고 "이 코드를 보니... 타입을 모든 곳에 지정하고 코드가 단정적이기 때문에 코드를 살펴보고 "여기서는 이것이 절대 참일 수 없습니다. 따라서 경고를 발생시키겠습니다."라고 말할 수 있는 도구가 있습니다. 타입 시스템은 "이것이 참이 아닐 수 있습니다. 따라서 증명할 수 없습니다. 따라서 경고를 발생시키겠습니다."라고 말합니다. 타입 시스템은 "이것이 참이 아닐 수 있습니다. 증명할 수 없습니다. 따라서 경고를 발생시키겠습니다."라고 말합니다. 이 도구는 "100% 오류가 있다는 것을 증명할 수 있습니다. 따라서 경고를 발생시키겠습니다."라고 말합니다.
크리스 젠킨스: 네, 알겠습니다.
José Valim: 이 도구가 있지만 몇 가지 문제가 있습니다. Dialyzer에는 몇 가지 문제가 있습니다. 첫째, 오류 메시지를 이해하기가 매우 어렵습니다. 오류 메시지가 정확하지 않습니다. 그리고 별도의 도구이기 때문에 실행해야 합니다. 실행하는 데 시간이 좀 걸립니다. 제 생각에는 이 도구를 컴파일러의 일부로 만들고, 빠르게 만들고, 좋은 오류 메시지를 제공한다면 커뮤니티의 대부분이 만족할 것이라고 생각합니다. 많은 사람들이 타이핑을 원하는 이유는 타입이 모든 곳에 있기 때문입니다. 그리고 이 도구를 보면 "이 도구를 개선할 수 있을 것 같아."라고 생각할 수 있습니다. 수영장 주변을 맴돌기만 하고 뛰어들지 않는 것과 같습니다. 그런 상황이었습니다. 그러던 중 우루과이의 석사 과정 학생 두 명이 Elixir에 대한 점진적 타이핑을 연구하고 있었기 때문에 저에게 논문을 보내왔습니다. 저는 타이핑에 관심이 있었지만 어떻게 해야 할지 몰랐습니다. 그래서 석사 과정 학생들에게서 이 논문을 받았습니다. "정말 흥미롭군. 함께 작업해서 이 아이디어를 발전시킬 방법을 찾아볼까?"라고 생각했습니다. 하지만 그들은 "아니요, 저희는 대학교를 졸업했습니다. 더 이상... 당신과는 아무 상관이 없습니다. 저희는 대학교를 졸업했습니다."라고 말했습니다.
크리스 젠킨스: 박봉의 대학원생으로 일하고 싶지 않았던 거군요.
José Valim: 네, 그들은 "저희는 끝났습니다."라고 말했습니다. 하지만 논문을 살펴보니 참고 문헌의 대부분이 점진적 타이핑 분야의 저명한 연구자인 Giuseppe Castagna의 연구였습니다. 그의 연구에 대한 링크가 많았습니다. 제가 Elixir가 완성되었다고 발표했을 때 실제로 Elixir에 대한 타입 시스템을 구현하려고 시도했습니다. 기존 언어에 대한 타입 시스템을 구현하는 것이 어려운 이유는 타입 시스템이 본질적으로 작성할 수 있는 코드 유형을 제한하기 때문입니다. "이 코드는 유효해 보이지만 타입 시스템은 증명할 수 없습니다. 타입 검사를 수행하지 않겠습니다. 실행할 수 없습니다."라고 생각할 수 있는 코드를 작성할 수 있습니다. 하지만 이미 모든 의미 체계가 자리 잡은 기존 언어가 있는 경우에는 그럴 수 없습니다. 의미 체계가 이미 자리 잡았습니다. 따라서 언어에 맞는 타입 시스템을 찾아야 합니다. 타입 시스템이 항상 "그렇게 할 수 없습니다."라고 말한다면 제대로 작동하지 않는 타입 시스템을 만들거나 언어를 제한해서 "타입을 사용할 때는 더 이상 그렇게 할 수 없습니다."라고 말해야 합니다. 언어를 제한한다면 여전히 같은 언어일까요? Elixir에 있는 모든 기능을 사용할 수 없다면, 즉 Elixir에 있는 모든 기능을 사용할 수 없다면 어느 정도까지 기능을 사용할 수 있을까요? "이제는 Elixir가 아닙니다. 다른 것입니다. Elixir의 하위 집합입니다."라고 말해야 할 때까지 말입니다.
크리스 젠킨스: 네, 언어의 규칙과 해당 규칙을 설명하는 언어인 타입 시스템 사이의 불일치입니다. 규칙을 표현하는 언어로 시작하면 해당 타입 시스템을 준수하도록 제한된 언어가 됩니다. 규칙이 유기적으로 진화하는 언어로 시작하면 규칙 작성 시스템이 해당 규칙을 설명할 수 있을 만큼 표현력이 있을까요?
José Valim: 네, 타입 시스템 프로토타입을 만들었을 때 저는 이 분야에 완전히 문외한이었고, 당시 몇몇 타입 시스템 연구자들과 이야기를 나누었습니다. 그러다가 교차 유형이라는 것을 알게 되었습니다. "몇 가지 작업을 수행했습니다."라고 생각했습니다. 타입 시스템이 매우 제한적이었습니다. 많은 것을 제한해야 했습니다. 예를 들어, 타입 추론과 같은 기능을 갖고 싶다면 매우 비용이 많이 들 것입니다. 프로토타입을 만들고 John Hughes에게 보여주었습니다. "이것을 구현했는데, 어느 정도 작동합니다."라고 말했습니다. John Hughes는 교차 유형에 대해 잘 알고 있었습니다. "어느 정도 작동하지만 매우 느립니다."라고 말했습니다. John Hughes는 "네, 교차 유형은 추론 속도가 매우 느린 것으로 알려져 있습니다."라고 말했습니다. 저는 "논문을 많이 읽었지만 그런 내용을 언급하는 논문은 본 적이 없습니다."라고 말했습니다. 어쨌든, "음..." Elixir 팀의 Eric도 이러한 것들을 조사하고 있었고, 저희는 문제에 봉착했습니다. 그러다가 점진적 타이핑에 대한 논문을 받았고 참고 문헌을 읽어보니 Giuseppe Castagna의 연구가 있었습니다. "그의 논문을 몇 개 읽어봐야겠다. 더 자세히 알아봐야겠다."라고 생각했습니다. 논문을 읽기 시작했는데 정말 재미있었습니다. 논문에는 "이 논문에서는 X, Y, Z 문제를 해결하는 방법을 보여주겠습니다."라고 적혀 있었습니다. X, Y, Z는 제가 타입 시스템 프로토타입을 구현하려고 할 때 겪었던 문제였습니다. Giuseppe Castagna는 문제를 완벽하게 설명했습니다. 저는 매우 흥분했습니다. "됐다! 이거야!"라고 생각했습니다. 논문은 문제를 소개하고 "이 문제를 수학적 속성으로 공식화할 수 있습니다."라고 말했습니다. 그리고 수학으로 넘어갔습니다. 수학 세계에서 해결책을 설계하고 모든 것이 정확하다는 것을 증명했습니다. 그리고 "여기, 작동하는 것을 보여줍니다."라고 말했습니다. 그리고 다시는 돌아오지 않았습니다. "이것을 구현하는 방법은 다음과 같습니다. 이렇게 보일 것입니다."와 같은 내용은 없었습니다. 그래서 Giuseppe에게 이메일을 보냈습니다. 그는 이 이야기를 듣고 유머 감각을 발휘했습니다. "네, 학회에 발표할 논문을 쓸 때는 구현에는 관심이 없습니다. 이론에만 관심이 있습니다."라고 말했습니다.
크리스 젠킨스: 네, 알겠습니다.
José Valim: 하지만 구현에 대해 이야기하는 논문이 있습니다. 저희는 "누군가를 찾아서 이 작업을 해야 할 것 같아."라고 이야기하기 시작했습니다. 그리고 박사 과정 학생인 Guillaume Duboc을 고용해서 Elixir에 대한 타입 시스템을 구현할 수 있는지, 무엇을 표현할 수 있고 무엇을 표현할 수 없는지, 어떤 절충안이 있는지 연구했습니다. 그리고 논문을 발표했습니다. 논문에는 "언어의 많은 부분을 매핑했습니다. 가장 큰 과제는 언어의 많은 부분을 타입 시스템에 매핑하는 것이었습니다."라고 적혀 있습니다. Guillaume과 Beppe는 이론이 없는 부분에서 새로운 이론을 만들었습니다. Elixir에 대한 타입 시스템을 만들기 위해 새로운 타입 시스템 이론이 탄생했습니다. 그것이 제가 50% 틀렸던 이유입니다. 지난 5년 동안 Elixir에 대해 가장 많이 작업한 것은 바로 지금입니다. Elixir에 대한 타입 시스템을 구현하고 있기 때문입니다. 3주 전에 출시된 Elixir 릴리스는 타입 시스템 기능을 갖춘 최초의 Elixir 릴리스였습니다. 농담으로... 타입 시스템에 대해 이야기할 것이 많습니다. 첫째, 정적 세계와 동적 세계를 연결해야 하기 때문에 점진적 타입 시스템입니다. 하지만 TypeScript와 같은 점진적 타입 시스템은 아닙니다. 점진적 타입 시스템이라고 하면 대부분의 사람들은 TypeScript를 떠올립니다. 실제로는 꽤 다릅니다. 점진적 타입 시스템이지만 언어의 일부로 점진적으로 구현하고 있습니다. 점진적 점진적 타입 시스템이라고 할 수 있습니다. 농담으로 말씀드렸지만 많은 혼란을 야기했습니다. 더 이상 이 주제에 대해 이야기해서는 안 되지만, 지금 이야기하고 있습니다. 목표는 바로 그것입니다. 다시 말씀드리지만, Elixir 코드는 단정적이기 때문에 오늘날 존재하는 코드에서 많은 타입 정보를 얻을 수 있습니다. 타입 시스템의 처음 두세 번의 릴리스에서는 이미 작성된 코드를 살펴보고 타입 정보를 수집하고 타입 검사를 수행했습니다. 즉, 정적 분석 코드를 실행할 것입니다. 한 줄도 변경할 필요가 없습니다. 타입 주석을 추가할 필요도 없고 아무것도 할 필요가 없습니다. 저희가 코드에서 버그를 찾아드릴 것입니다. 무료로 말입니다. 이것이 작동한다면 타입 주석을 도입하는 것을 고려할 수 있습니다. 하지만 저희는 타입 시스템을 시험해 보고, 구현해 보고, 대규모 코드베이스에서 성능에 미치는 영향을 확인하고 싶습니다. 오류 메시지를 개선하고 사용자에게 제공하는 초기 경험이 정말 좋은 경험이 되도록 하고 싶습니다. 작동한다면 점진적으로 더 많은 기능, 타입 주석 등을 도입할 것입니다. 많은 작업이 필요합니다. Elixir는 정적으로 타입이 지정된 프로그래밍 언어가 되는 방향으로 나아가고 있습니다. 점진적 타입 시스템이 있고 동적 타입을 전혀 사용하지 않는다면 정적으로 타입이 지정된 프로그래밍 언어입니다. 하지만 아직 몇 년 더 작업해야 합니다.
크리스 젠킨스: 네, 알겠습니다. 하지만 Dialyzer보다 빠르고 더 지능적이라는 목표는 이미 달성했다고 생각하시나요?
José Valim: 네, 그렇습니다. 아직은 아닙니다. 아직 그 단계에 이르지 못했습니다. 새 버전을 출시했을 때 사람들은 "Alex가 제 코드에서 버그를 찾았습니다."라고 말했습니다. 좋습니다. 저희는 Dialyzer가 하지 못하는 일을 하기 시작했습니다. 사람들의 코드베이스에서 버그를 찾고 있습니다. 좋습니다. 저희는 Dialyzer가 하는 일의 일부를 할 수 있지만, Dialyzer가 하는 모든 일을 할 수는 없습니다. 무슨 말인지 설명해 드리겠습니다. Dialyzer의 목표는 "타입 시스템이 아닙니다. 100% 확신할 수 있다면... 즉, 버그가 있다는 것을 100% 확신할 수 있다면 경고를 발생시키겠습니다."라고 말하는 것입니다. Dialyzer는 절대 틀리지 않습니다. Dialyzer가 경고를 발생시키면 코드에 문제가 있는 것입니다. 앞서 말씀드렸듯이 타입 시스템은 그렇지 않습니다. 타입 시스템은 "여기서 뭔가 잘못된 것 같습니다. 아니면 이것이 정확하다는 것을 증명할 수 없습니다. 런타임에는 괜찮을 수도 있습니다."라고 말합니다. 이것이 작동할 것이라고 말할 만한 충분한 증거가 없습니다.
크리스 젠킨스: 네, 그렇습니다.
José Valim: 네, 저희가 하는 일은 바로 그것입니다. 타입 시스템을 만들고 있기 때문에 Dialyzer가 잡아내는 모든 버그를 잡아낼 수는 없습니다. 알겠죠? 하지만 타입 주석을 추가하고 정적 타입을 사용하면 코드에 타이핑 위반이 없다는 것을 증명할 수 있다는 보장을 제공할 수 있습니다. Dialyzer는 그렇게 할 수 없습니다. Dialyzer는 타입 위반이 있다는 것을 증명할 수 있습니다. Dialyzer는 코드에 타이핑 위반이 없다는 것을 증명할 수 없습니다.
크리스 젠킨스: 네, 알겠습니다.
José Valim: 타입 시스템은 "이 코드에는 타이핑 위반이 없습니다."라고 말할 수 있습니다. 저희는 Dialyzer의 일부를 가져왔다고 말할 수 있습니다. Dialyzer가 하는 모든 일을 할 수는 없지만, Dialyzer가 하는 일의 상당 부분을 가져와서 타입 시스템에 포함할 수 있습니다. 따라서 어느 시점에는 타입 시스템으로 업그레이드해서 타입 시스템의 이점을 얻을 수 있습니다. 작동 방식은 매우 흥미롭습니다. 적어도 저는 매우 흥미롭다고 생각합니다. 대부분의 동적 프로그래밍 언어에는 공용체가 있습니다. "이 코드는 정수 또는 문자열을 받을 수 있습니다."라고 말합니다. JavaScript에서는 typeof
를 사용해서 특정 유형인지 확인할 수 있습니다. 메서드가 있는지 확인할 수도 있습니다. 하지만 공용체를 전달할 수 있습니다. 타입 시스템에서는 공용체가 있으면 타입 시스템이... "이 항목은 정수 또는 문자열을 받을 수 있습니다."라고 말하면 코드를 작성할 때... 정수 또는 문자열을 반환하는 함수가 있다고 가정해 보겠습니다. 해당 함수의 호출자는 두 반환 유형을 모두 처리해야 합니다. "정수를 받으면 이렇게 처리하고 문자열을 받으면 이렇게 처리합니다."라고 말해야 합니다. 공용체의 양쪽을 모두 처리해야 합니다. 런타임에 제공하는 인수를 살펴보면 해당 항목이 절대 문자열을 반환하지 않는다는 것을 100% 확신할 수 있더라도 "여기서 문자열을 받을 수 있습니다."라고 말해야 합니다. 해당 항목이 예외를 발생시키도록 만들 수도 있습니다. 여기에 도달할 것으로 예상하지 못했지만, 이러한 것들을 처리해야 합니다. 처리해야 합니다. Elixir 타입 시스템에서 점진적 타이핑은 다음과 같이 작동합니다. 동적... 동적 타입이 있습니다. 일반적으로 점진적 타입 언어에서 동적 타입은 와일드카드입니다. 조커입니다. "이 항목은 무엇이든 될 수 있습니다. 동적입니다. 무엇인지 모릅니다. 무엇이든 될 수 있습니다. 많은 경우 최상위 유형입니다. 무엇이든 될 수 있습니다. 모든 것이 동적입니다."라고 말하는 것입니다. Elixir 타입 시스템에서 동적은 실제로 범위입니다. 동적은 최상위 유형이 아닙니다. 동적은 범위입니다. "정수 또는 문자열이 있으면 둘 다 처리해야 합니다."라고 말할 수 있습니다. 하지만 이 공용체에 동적을 지정하면 "둘 다 처리할 필요가 없습니다. 적어도 하나만 처리하면 됩니다."라고 말하는 것입니다. 코드에 동적을 지정하면 코드는 "정수를 처리하면 괜찮습니다. 문자열을 처리하면 괜찮습니다. 둘 다 처리하면 괜찮습니다. 하지만 둘 다 처리하지 않으면 오류를 발생시키겠습니다. 둘 중 하나여야 하기 때문입니다."라고 말하는 것입니다. 둘 중 하나가 아니라면 100% 오류입니다. Dialyzer가 작동하는 방식입니다. Dialyzer는 "두 가지 가능성을 모두 처리하지 않고 있습니다."라고 말합니다. 저희는 이러한 작업을 수행할 수 있다는 것을 깨달았을 때 매우 흥분했습니다. 정적 타이핑 코드를 동적 없이 사용하려면 타입 시스템처럼 작동해야 합니다. 하지만 추론과 같이 사용자에게 "여기에 버그가 있을 수 있습니다. 증명할 수 없습니다."라고 말하고 싶지 않다면 동적을 사용할 수 있습니다. "모든 것을 포기합니다."라는 의미가 아닙니다. 버그가 있다는 것을 증명할 수 있다면 여전히 버그를 찾으려고 노력할 것입니다. 저는 이것이 매우 흥미롭다고 생각합니다. "오..."와 같이 들리고 싶지는 않지만... 아무도 이것을 연구하고 있지 않습니다. 저희만 연구하고 있습니다. Elixir 의미 체계에 완벽하게 맞는 정말 멋진 것을 발견했을 수도 있기 때문에 매우 흥미롭습니다. 다른 시스템의 미래를 바꿀 수도 있습니다. 하지만 재앙이 될 수도 있습니다. "뭔가 멋진 것을 발견했다고 생각했는데..." 3년 후에는 "아..." 막다른 골목에 도달했습니다. 저희만 이 길을 탐험하고 있었습니다. 도움을 요청할 사람이 아무도 없습니다. 저희만 여기에 있기 때문입니다. 이러한 문제에 대해 생각하고 개발자와 어떻게 소통할지, 적절한 절충안을 마련할지 고민하는 것은 흥미로운 일입니다. 결정해야 할 것이 많지만 흥미롭다고 생각합니다.
크리스 젠킨스: 네, 수학적 타이핑에서 경찰 타이핑으로 넘어가고 있습니다. 증거는 없지만 증거를 수집하면서 진행합니다.
(웃음)
José Valim: 네, 저희가 만들고 있는 타입 시스템은... 수학에 대해 이야기하고 있었는데, 저희가 만들고 있는 타입 시스템은 집합 이론적 타입이라고 합니다. 집합 이론적 타입이 마음에 드는 이유는 모든 것이 집합 연산으로 이루어지기 때문입니다. 모든 것입니다. 특히 TypeScript에는 공용체, 교집합, 부정이 있습니다. TypeScript에는 이러한 것들이 있습니다. 저희 타입 시스템에도 비슷한 것이 있습니다. Typed Racket과 같은 다른 프로그래밍 언어에도 공용체, 교집합, 부정이 있습니다. 하지만 저희 타입 시스템은 의미론적 하위 타이핑을 사용하기 때문에 구현 방식이 다릅니다. 의미론적 하위 타이핑은 전체 타입 시스템이 이러한 연산 위에 구현된다는 것입니다. 예를 들어, 두 타입이 호환되는지 확인하려면 함수를 호출합니다. "이 함수가 제가 예상하는 것을 받아들일까요?"라고 묻는 것입니다. 이를 확인하는 방법은 타입을 집합으로 표현하는 것입니다. 해당 집합의 교집합을 구합니다. 교집합이 비어 있으면 타입 검사가 작동하지 않는다는 것을 의미합니다. 해당 집합에는 공통점이 없습니다. 저는 이 방식이 구현하고 아이디어를 논의하기에 더 간단하다고 생각합니다. 모든 것이 교집합, 공용체, 부정에 관한 것이기 때문입니다. 코드로 변환하기 쉬운 간단한 이론적 모델입니다.
크리스 젠킨스: 네, Elixir의 모든 타입을 이 집합으로 표현하고 정의해야 한다는 매우 복잡한 문제가 있습니다. 해당 항목 사이의 공용체 또는 교집합을 수행할 때 올바른 수학적 속성을 나타내야 한다는 것을 수학적으로 증명해야 합니다. 하지만 일단 데이터 구조를 구현하면 수학적인 부분은 끝입니다. 데이터 구조를 구현하는 것입니다. 데이터 구조를 구현하면 의미 체계는 명확해집니다. 누구나 이해할 수 있는 공용체, 교집합, 부정입니다. Beppe와 Guillaume은 데이터 구조를 작업하고 있습니다. 그들은 저에게 데이터 구조를 제공합니다. 그러면 저는 Elixir 컴파일러에서 해당 데이터 구조를 사용할 수 있습니다. "이것은 교집합인가요?"라고 묻기만 하면 됩니다. 사용하기 정말 쉽습니다. 그것이 또 다른 흥미로운 부분입니다. 어떻게 설명해야 할지 모르겠지만, 제가 말씀드린 동적 항목도 기대하는 모든 수학적 속성을 갖춘 데이터 구조를 드러내고 있습니다. 정말 매력적입니다. 저는 어떻게 작동하고 증명되는지 이해할 수 없지만, 해시 딕셔너리나 맵을 가진 프로그래밍 언어가 있다면 내부적으로 메모리에서 해당 항목이 수행하는 작업은 버킷을 만드는 것입니다.
크리스 젠킨스: 네, 적흑 트리입니다. 비트 연산을 사용합니다. 많은 일들이 일어나지만 저희는 신경 쓰지 않습니다. 복잡성은 추상화되어 있습니다. 여기서도 마찬가지입니다.
José Valim: 저는 수학을 이해하고 이러한 모든 작업을 수행하기 위해 박사 학위를 받아야 했다면 모든 것이 느려졌을 것이기 때문에 기쁩니다. 하지만 프로그래머로서 사용할 수 있는 표면 수준의 도구가 있습니다. 언어 설계자에서 작업이 끝났다고 생각하는 사람, 다른 라이브러리를 사용하는 사람, 다시 언어 설계자가 된 사람으로 변화하는 과정이 재미있습니다. 그리고 매우 열정적으로 보입니다.
크리스 젠킨스: 네, 정말 열정적으로 보입니다.
José Valim: 네, 저는 이런 일들을 즐깁니다. 저에게는... 많은 것을 배우고 있기 때문입니다. 그것이 제가 이런 일들에 흥미를 느끼는 이유입니다. 어떤 면에서는 항상 "나이가 들면... 12년 후에는... '아, 이제 지겹다.'라고 생각하게 될까?"라는 걱정을 했습니다. 하지만 정반대의 효과가 나타났습니다. 무언가를 배울 때마다 "12년 동안이나 있었는데 몰랐다니!"라고 생각합니다. 지겹기는커녕 "아, 영원히 존재했는데 이제야 발견했구나!"라고 생각합니다.
크리스 젠킨스: 네, 맞습니다. Phoenix 웹 프레임워크의 제작자인 Chris McCord의 Flame이라는 멋진 프로젝트가 있습니다.
José Valim: 네, Flame은 출시 당시 200~300줄 정도의 코드로 이루어져 있었습니다. 문서를 포함해서 1,000줄이 채 되지 않았습니다. Flame은 필요에 따라 확장할 수 있습니다. Elixir 코드에서 새 노드를 시작하고 해당 노드에 코드를 보내서 실행하고 결과를 다시 받을 수 있습니다. Erlang 가상 머신에서 이를 쉽게 구현할 수 있는 이유는 Erlang이 항상 분산 실행을 지원했기 때문입니다. 분산 실행은 Erlang의 일부이고 Elixir의 일부입니다. Flame은 "Firecracker와 같이 노드와 머신을 매우 빠르게 시작할 수 있는 것을 사용할 수 있습니다."라고 말합니다. Flame은 Firecracker와 같은 것을 사용해서 노드를 매우 빠르게 시작하고 해당 노드에서 작업을 수행하고 결과를 반환합니다. 메인 머신에서 수행하고 싶지 않은 비싼 작업을 수행하려는 경우에 유용합니다. 머신을 매우 빠르게 생성하고 해당 머신에서 작업을 실행하고 머신이 종료되면 결과를 반환합니다. 풀을 만들 수 있고 쉽게 확장할 수 있습니다. 코드를 확장할 수준을 결정할 수 있기 때문에 좋습니다. 일반적으로 "확장하고 싶다면 Lambda를 사용해야 합니다. Amazon Lambda를 사용할 수 있지만 API와 통신해야 합니다. 따라서 요청을 직렬화하고 큐에 넣거나 Lambda로 보내야 합니다. Lambda는 결과를 가져와서 S3에 넣고 다시 읽습니다." 많은 작업을 수행해야 합니다. Flame은 "노드를 시작하고 메모리에 있는 데이터 구조를 보내겠습니다. Erlang은 그렇게 할 수 있습니다. 모든 Erlang 데이터 구조는 직렬화 가능합니다. 결과를 다시 받으면 됩니다. 행복합니다."라고 말합니다. Flame은 작년에 출시되었고, 저희는 "8년 전에도 출시할 수 있었을 텐데..."라고 이야기했습니다. "여기에 있는 것은 8년 전, 10년 전에도 존재했던 것입니다. 항상 여기에 있었습니다." 저에게는 매력적입니다. "또 무엇을 발견할 수 있을까? 우리를 기다리고 있는 숨겨진 것들은 무엇일까?"라는 생각이 듭니다.
크리스 젠킨스: 네, 알겠습니다. 프로그래밍은 결코 지루해지지 않는 직업이라고 생각합니다. 항상 새롭게 발견할 것이 있기 때문입니다. Flame이 그런 일을 하고 있다니 정말 좋습니다. 언젠가 Chris McCord를 초대해서 Flame에 대해 이야기해야겠습니다. 멋진 시스템인 것 같습니다.
José Valim: 네, Chris McCord도 기꺼이 참여할 것입니다. Phoenix와 Flame에 대해 이야기할 것이 많습니다.
크리스 젠킨스: 좋습니다. 이제 마지막 주제입니다. 해야 할 일과 발견해야 할 것이 많으실 테니 이제 마무리하겠습니다. 2012년부터 오늘까지의 여정에 대해 이야기했지만, Elixir의 현재 여정은 어떤가요? 오늘날 Elixir는 누구를 위한 언어인가요? Elixir의 현재 상태는 어떤가요?
José Valim: Elixir는 웹 개발자를 위한 언어입니다. 대부분의 사람들이 웹 개발에 Elixir를 사용하고 있습니다. Elixir의 약속은... 제가 항상 하는 말을 하겠습니다. Elixir의 약속은 운영의 단순성입니다. 오늘날 웹 애플리케이션을 구축하는 대부분의 사람들은 복잡성을 더하고 있습니다. 웹 애플리케이션으로 시작해서 데이터베이스를 추가합니다. 데이터베이스를 추가해야 합니다. 그리고 백그라운드 처리를 하고 싶어서 RabbitMQ, SQS와 같은 라이브러리나 시스템을 가져옵니다. 그리고 실시간 메시징을 하고 싶어서 또 다른 서비스를 가져옵니다. 때로는 인증을 위해 외부 공급자에게 비용을 지불해야 하는 경우도 있습니다. 이렇게 할 때마다 운영 복잡성이 증가합니다. 그리고 모든 것을 우리에게서 멀리 떨어뜨리고 있습니다. 이제 세 가지 서비스가 있습니다. 이러한 서비스를 실행하려면 프로덕션 환경에 배포하고 관리해야 합니다. 테스트를 실행하려면 RabbitMQ 또는 SQS의 모의 버전이 필요합니다. 버그가 있으면 프로덕션 환경에서 발견될 것입니다. 테스트 중에는 발견되지 않을 것입니다. 많은 복잡성이 있습니다. 우리는 끊임없이 외부에 의존하고 있습니다. 외부 종속성을 가져오고 있습니다. 제어권을 포기하고 있습니다. 이는 개발에 복잡성을 더합니다. 이제 세 가지 다른 서비스를 사용해서 무언가를 실행해야 합니다. 모든 프로젝트는 Docker Compose로 시작해서 네다섯 가지를 실행합니다. 많은 복잡성이 있습니다. 프로그래머는 종속성이라는 단어를 가볍게 여기는 유일한 사람들이라고 생각합니다. 인생의 다른 모든 곳에서는 누군가가 "이 종속성을 원하시나요? 뭔가 잘못되면 책임을 져야 하고 처리해야 하는 것을 원하시나요?"라고 묻습니다. 프로그래머는 "네, 100개 주세요."라고 대답합니다. 바로 여기입니다. 많은 사람들이 그렇게 생각합니다. 많은 프로젝트의 일상을 설명하는 것입니다. Elixir의 목표는 Elixir와 데이터베이스만 있으면 된다는 것입니다. 그게 전부입니다. 물론 Elixir 라이브러리를 사용할 것입니다. 하지만 웹 개발자에게는 Elixir와 Phoenix만 있으면 됩니다. 분산 Pub/Sub입니다. 실시간으로 무언가를 하고 싶다면 "이 사용자, 친구가 참여했습니다."라는 메시지를 보낼 수 있습니다. 기본적으로 제공됩니다. 현재 상태입니다. 클러스터에 누가 연결되어 있는지 알고 싶다면 "친구가 참여했습니다. 친구가 온라인 상태입니다. 메시지를 보내세요."라는 메시지를 보낼 수 있습니다. 기본적으로 제공됩니다. 이러한 기능을 기본적으로 제공하는 프레임워크는 없습니다. 국제화 기능도 기본적으로 제공됩니다. 모니터링 대시보드도 제공됩니다. Erlang 가상 머신은 이러한 작업에 탁월합니다. 시스템에서 실행 중인 모든 코드, 메모리에 있는 항목, 사용 중인 메모리 양을 확인할 수 있는 대시보드가 있습니다. 모든 것이 있습니다. 기본적으로 제공되는 기능이 매우 많습니다. LiveView라는 프로젝트를 사용하면 서버에서 제어하는 대화형 실시간 웹 애플리케이션을 만들 수 있습니다. 제 회사 블로그인 Dashbit.co나 Phoenix 프레임워크 웹사이트에 가면 이러한 솔루션이 작동하는 방식에 대한 기사가 많이 있습니다. 모든 것이 기본적으로 제공됩니다. 궁극적으로 모든 것을 제어할 수 있습니다. 모든 것이 경험의 일부입니다. 프로덕션 환경에서 실행하면 여러 코어로 확장됩니다. 분산 기능이 작동하기 때문에 수평적으로 확장할 수도 있습니다. 많은 것을 제공하고 전반적인 복잡성을 줄여줍니다. 그것이 바로 Elixir의 장점입니다. "이 모든 것을 얻을 수 있지만 이제 저수준 코드를 작성해야 합니다."라고 말하는 것이 아닙니다. Elixir는 고수준의 표현력이 풍부한 언어입니다. 앞서 이야기했듯이 웹 개발에는 Elixir가 정말 좋습니다. 필요한 모든 것을 제공하면서도 생산성을 유지하고 원하는 대로 만들 수 있습니다. 가장 쉬운 설명입니다. 오늘날 대부분의 Elixir 개발자를 끌어들이는 요소입니다. 대부분의 개발자는 Elixir를 웹 개발에 사용합니다. 하지만 Elixir는 확장 가능한 언어입니다. Elixir가 웹 전용 언어가 되기를 원하지 않았기 때문입니다. 웹에 특화된 언어가 되기를 원하지 않았습니다. 범용 프로그래밍 언어가 되기를 원했습니다. 시간이 지나면서 Elixir 커뮤니티에서 많은 일들이 일어났습니다. Nerves는 고급 임베디드 소프트웨어를 구축하는 데 사용됩니다. 이러한 작업을 수행하기 위한 프레임워크가 있습니다. 오디오 및 비디오 스트리밍, 데이터 기반 파이프라인 구축에도 사용됩니다. 이러한 모든 문제를 해결하기 위한 라이브러리가 있습니다. 오늘 여기서 이야기한 가장 최근의 것은 Numerical Elixir입니다. Numerical Elixir는 Elixir에서 머신 러닝, 딥 러닝, 신경망을 구현하고 GPU로 컴파일하고 실행할 수 있도록 하는 것입니다. 그리고 Livebook이 있습니다. Jupyter Notebook을 사용해 본 적이 있다면 Livebook은 Jupyter Notebook과 비슷하지만 Elixir에 맞게 조정되었고 Elixir의 강점을 활용합니다. 이 분야에서 많은 작업을 해 왔지만 아직 새로운 분야입니다. Elixir와 이 플랫폼이 얼마나 훌륭하고 독특할 수 있는지 보여주는 부분이 있다고 생각합니다. "Elixir와 Livebook, 동시성, 웹 동시성, AI의 만남"이라는 발표를 했습니다. Livebook, Elixir, 동시성, AI를 검색하면 아마 나올 것입니다. 링크를 찾아서 쇼 노트에 넣어두겠습니다.
크리스 젠킨스: 네, 쇼 노트에 넣어두겠습니다.
José Valim: 이 발표에서 저는 Livebook을 소개합니다. 처음 20분 동안은 Livebook, 즉 Elixir의 코딩 노트북 플랫폼을 소개합니다. 그리고 다음 20분 동안은 클러스터에서 분산 머신 러닝을 수행하는 웹 애플리케이션을 라이브 코딩합니다. 20분 만에 라이브 코딩을 합니다.
크리스 젠킨스: 재미있네요.
José Valim: 네, "머신 러닝 모델을 제공하는 별도의 솔루션을 가져왔기 때문에 작동합니다."와 같은 것이 아닙니다. Elixir에서 모든 것이 실행되기 때문에 작동합니다. 외부 종속성이 없습니다. Elixir에서 모든 것이 실행됩니다. Elixir를 사용해서 머신 러닝을 수행하는 다른 머신과 통신합니다. 모든 것이 있습니다. Phoenix에 대해 말씀드린 것과 직결됩니다. 우리는 많은 것을 할 수 있고, 그 대부분은 Elixir의 공이 아닙니다. Erlang의 공입니다. 거의 40년 동안 존재해 온 플랫폼입니다. 그리고 우리는 Erlang으로 할 수 있는 새로운 것을 계속 발견하고 있습니다. Numerical Elixir는 흥미로운 새로운 분야입니다. 관심이 있다면 시도해 보세요. 하지만 아직 개발 중이라는 점을 유의하세요. Python의 20~30년을 따라잡고 있습니다. 따라잡아야 할 것이 많습니다. 하지만 Elixir와 이 플랫폼이 얼마나 훌륭하고 독특할 수 있는지 보여주는 부분이 있다고 생각합니다.
크리스 젠킨스: 네, Beam만큼 많은 사랑을 받는 플랫폼은 본 적이 없습니다. Beam 위에 사용자 친화적인 언어를 만드는 것은 잠재력이 매우 큽니다. 잘 해내셨습니다. 저도 그 비디오를 보고 직접 시도해 봐야겠습니다. 이제 마무리해야겠습니다. 오늘 컴파일러와 언어 공간에 대해 이야기해 주셔서 감사합니다.
José Valim: 초대해 주셔서 감사합니다. 저는 이런 이야기를 하는 것을 정말 좋아합니다. 기회를 주셔서 감사합니다.
크리스 젠킨스: 네, 열정이 느껴집니다. 함께해 주셔서 감사합니다.
José Valim: 감사합니다.
크리스 젠킨스: 평소보다 긴 에피소드였습니다. 그래도 두 배는 더 이야기할 수 있었을 것 같습니다. 비슷한 생각이 든다면 쇼 노트에 있는 Gleam 에피소드 링크를 확인하세요. Beam 플랫폼에서 타입 프로그래밍에 대한 한 시간 분량의 내용을 더 들을 수 있습니다. 에피소드에서 논의한 다른 모든 것에 대한 링크도 쇼 노트에 넣어두겠습니다. 팟캐스트 길이에 대해서는 설문 조사를 해야 할 것 같습니다. 적절한 에피소드 길이는 얼마일까요? 한 시간? 더 길게? 더 짧게? 저는 항상 대화가 지속되는 만큼, 그 이상은 아니라는 규칙을 따랐습니다. 하지만 의견이 있다면 공유해 주세요. 이 에피소드가 마음에 들었다면 좋아요를 눌러주시고, 흥미로울 것 같다고 생각하는 친구가 있다면 공유해 주세요. 그리고 계속해서 듣고 싶다면 Patreon에서 Developer Voices를 후원해 주세요. Patreon은 최근에 개설했습니다. 쇼 노트에 링크가 있습니다. 지금까지 가입해 주신 모든 분들께 감사드립니다. 마케팅 부서를 설득해서 가입해 주신 분들께도 감사드립니다. 큰 도움이 됩니다. 다음 주에 더 많은 내용으로 돌아오겠습니다. 구독해서 놓치지 마세요. 다음 주까지, 저는 여러분의 호스트 크리스 젠킨스였습니다. José Valim과 함께한 Developer Voices였습니다. 들어주셔서 감사합니다.
(음악)