엘릭서 언어 디자인 목표
이 글은 조제 발림이 엘릭서 언어 공식 블로그에 올린 Elixir Design Goals라는 글의 전문을 번역한 글입니다.
엘릭서 언어 디자인 목표
August 08, 2013 · by José Valim . in Internals
작년 한 해 동안 엘릭서를 소개하기 위해 여러 컨퍼런스에서 발표를 했습니다. 보통은 얼랭 VM을 소개하는 것부터 시작해서 엘릭서의 목표에 대해서 이야기하고, 마지막에는 라이브 시연 시간을 마련해서 리모트 노드간 정보 교환이나 핫 코드 스와핑 같은 멋진 기능을 보여주었습니다.
이 글은 그런 발표를 요약한 글로, 엘릭서 언어 목표인 호환성, 생산성 및 확장성을 집중적으로 다룹니다.
호환성
엘릭서는 얼랭 VM 및 기존 생태계와 호환되도록 설계되었습니다. 얼랭은 보통 다음과 같은 세 부분으로 나눌 수 있습니다.
- 얼랭이라 불리는 함수형 프로그래밍 언어
- OTP라 불리는 디자인 원칙의 집합체
- EVM 또는 BEAM이라 불리는 얼랭 가상 머신
엘릭서는 얼랭과 동일한 가상 머신에서 작동하며 OTP와 호환됩니다. 그뿐 아니라 얼랭 생태계에서 사용 가능한 모든 도구와 라이브러리를 엘릭서에서도 사용할 수 있습니다. 얼랭에서 엘릭서를 호출하는 것과 그 반대 경우 모두에 전환 비용이 없기 때문입니다.
우리는 얼랭 VM이 엘릭서의 가장 큰 자산이라고 이야기하곤 합니다.
모든 엘릭서 코드는 가벼운 프로세스(액터) 내에서 실행되는데, 각 프로세스는 자신만의 상태를 가지고 있고 서로 메시지를 주고 받습니다. 얼랭 VM은 그런 프로세스들을 여러 코어에 분배해주어서 코드를 정말 쉽게 병렬적으로 실행할 수 있도로 해줍니다.
실제로 엘릭서 소스 코드를 비롯해 어떤 엘릭서 코드를 컴파일해도 기본적으로 머신의 모든 코어를 사용하는 것을 볼 수 있습니다. Parallella같은 기술이 더 저렴해지고 접근성이 증가하는 현 상황에서 얼랭 VM을 통해서 얻을 수 있는 능력을 간과하기는 어렵습니다.
마지막으로 얼랭 VM은 영원히 동작하고 스스로를 복구하며 스케일하는 시스템을 만들기 위해 디자인되었습니다. 얼랭의 개발자 중 한 명인 조 암스트롱이 OTP와 얼랭 VM의 디자인적 결정사항에 대해서 최근에 훌륭한 발표를 한 적이 있습니다.
우리가 위에 적은 내용은 특별히 새로운 것은 아닙니다. CouchDB, Riak, RabbitMQ, Chef11 등의 오픈소스 프로젝트와 Ericsson, Heroku, Basho, Klarna, Wooga 등의 기업은 얼랭 VM의 장점을 이미 활용하고 있습니다. 그 중에는 꽤 오래 전부터 이를 활용해온 곳도 있습니다.
생산성
이제 메타적인 주제로 넘어가 봅시다. 우리는 이제 언어 디자인을 언어 디자인에 대한 패턴이라고 생각해야만 합니다. 동일한 종류의 도구를 더 만들기 위한 도구 말입니다. […] 언어 디자인은 어떤 하나의 무엇인가여서는 안 됩니다. 언어 디자인은 패턴, 즉 성장을 위한 패턴이어야만 합니다. 패턴을 키워내기 위한 패턴이자, 프로그래머들이 자신의 실제 업무와 주 목표를 위해 사용하는 패턴을 정의할 수 있는 패턴이어야 합니다.
- 가이 스틸, 1998 ACM OOPSLA 컨퍼런스 “Growing a Language”라는 기조연설에서
생산성은 일반적으로 측정하기 어려운 목표입니다. 데스크탑 어플리케이션을 만들 때 생산적인 언어는 수학적 계산을 할 때에는 생산적이지 않을 수 있습니다. 생산성은 사용자가 언어를 사용하려고 하는 분야, 생태계 내에서 사용 가능한 도구, 그리고 해당 도구를 얼마나 쉽게 만들고 확장할 수 있는 지에 직접적으로 영향을 받습니다.
이런 이유로 우리는 언어의 핵심부를 작게 만들기로 결정했습니다. 예를 들어 일부 언어에서 if
, case
, try
등은 해당 언어의 파서에서 자신만의 규칙을 가진 키워드들이지만, 엘릭서에서는 모두 그냥 매크로일 뿐입니다. 덕분에 우리는 엘릭서의 대부분을 엘릭서를 사용해서 구현할 수 있었습니다. 또한 매크로는 개발자들이 우리가 언어를 만들기 위해서 사용했던 것과 동일한 도구를 사용해서 자신들이 작업하는 특정 분야에 맞추어 언어를 확장할 수 있도록 해줍니다.
여러 언어에 키워드로 구현되어 있는 unless
를 엘릭서에서 구현하는 예시입니다.
코드 표현을 인자로 받는 매크로 덕분에 우리는 컴파일 타임에 unless
를 if
로 간단히 변환할 수 있습니다.
매크로는 엘릭서의 메타프로그래밍, 즉 코드를 생성하는 코드를 작성할 수 있는 기능의 기본적인 구성요소이기도 합니다. 메타프로그래밍은 개발자가 손쉽게 보일러플레이트 코드를 제거하고 강력한 도구를 만들 수 있게 해줍니다. 발표에서 테스트 프레임워크의 표현력을 위해서 매크로를 사용한다는 예시를 자주 사용했었습니다. 한 번 그 예시를 살펴봅시다.
처음 눈에 띄는 것은 async: true
옵션입니다. 테스트에 사이드 이펙트가 없으면 async: true
옵션을 넣어서 병렬로 테스트를 실행할 수 있습니다.
다음으로 테스트 케이스를 정의하고 assert
매크로를 사용해서 어설션을 만듭니다. 그냥 assert
를 호출하면 별로 도움이 되지 않는 에러 리포트가 나올 것이기 때문에 여러 언어에서 권장되는 방법은 아닐 것입니다. 그런 언어에서는 해당 어설션을 수행할 때 assertEqual
이나 assert_equal
같은 함수나 메서드를 사용하는 것을 권장할 것입니다.
하지만 엘릭서의 assert
는 매크로이기 때문에, 어설션의 대상이 되는 코드를 살펴보고 해당 코드가 비교를 실행한다는 것을 추론할 수 있습니다. 그렇게 분석한 후 해당 코드를 변환하여, 테스트가 실행되면 구체적인 에러 리포트를 제공하도록 만듭니다.
이 간단한 예시에서 개발자가 매크로를 사용해서 간결하지만 강력한 API를 제공할 수 있는 방법을 볼 수 있습니다. 매크로는 컴파일 환경 전체에 접근할 수 있기 때문에 임포트된 함수, 매크로, 정의된 변수 등을 확인하고 사용할 수 있습니다.
위 예시는 매크로를 사용해서 엘릭서에서 할 수 있는 것들의 극히 일부분일 뿐입니다. 예를 들어 우리는 매크로를 통해서 표현력이 높지만 고도로 최적화된 라우팅 알고리즘을 제공하고, 이를 사용해서 웹 어플리케이션의 라우트를 VM에서 고도로 최적화할 수 있는 패턴의 집합체로 컴파일하고 있습니다.
매크로 시스템은 또한 문법에도 큰 영향을 미쳤습니다. 마지막 주제로 넘어가기 전에 문법에 대해서 간략히 이야기하도록 하겠습니다.
문법
문법은 엘릭서에 대해서 이야기할 때 보통 가장 먼저 나오는 주제 중 하나이긴 하지만, 우리의 목표는 단순히 다른 문법을 제공하는 것이 아닙니다. 우리는 매크로 시스템을 제공하고 싶었고, 그러기 위해서는 엘릭서의 자체적인 데이터 구조를 사용해서 엘릭서 문법을 명시적으로 표현할 수 있어야만 했습니다. 이런 목표를 염두에 두고 우리가 설계한 최초의 엘릭서 버전은 다음과 같이 생겼었습니다.
위 코드에서는 변수를 제외한 모든 것을 함수 또는 매크로 호출로 표현했습니다. 첫 버전에서부터 do:
같은 키워드 인자가 존재했다는 점에 주목해주세요. 우리는 여기에다가 새로운 문법을 천천히 추가해서, 자주 사용하는 패턴을 보다 세련되게 만드는 동시에 그 기반이 되는 데이터적 표현의 동일성을 유지했습니다. 연산자에도 곧 인픽스 표기법을 추가했습니다.
괄호를 선택사항으로 만드는 것이 다음 단계였습니다.
그리고 마지막으로 자주 사용되는 do: (...)
구성요소를 더 간편하게 쓸 수 있도록 do/end
를 추가했습니다.
제게 루비 경력이 있는 만큼 추가된 구성요소 중 일부를 루비에서 빌려오는 것은 자연스러운 수순이었습니다. 하지만 그렇게 추가된 부분은 부산물이었지 언어의 목표는 아니었습니다.
언어 구문 중 여럿은 또한 그에 대응되는 얼랭 구성요소에서 따왔습니다. 제어 흐름 매크로, 연산자, 컨테이너 등이 그 예시입니다. 일부 엘릭서 코드가 어떻게 얼랭에 대응하는지 보세요.
확장성
엘릭서는 작은 핵심부에 기반을 두고 만들어진 언어이기 때문에, 개발자들이 개발 대상으로 삼은 특정 분야에 따라 언어 구성요소의 대부분을 필요한 대로 교체하고 확장할 수 있습니다. 하지만 엘릭서가 본질적으로 뛰어난 특정 분야가 있는데, 이는 병렬 분산 어플리케이션을 만드는 분야입니다. OTP와 얼랭 VM 덕분입니다.
엘릭서는 다음과 같은 특징을 가진 표준 라이브러리를 제공하여 이 분야를 더욱 보완합니다.
- 유니코드 문자열 및 유니코드 연산
- 강력한 유닛 테스트 프레임워크
- 새롭게 구현한 셋과 딕셔너리, 그리고 레인지 같은 추가적인 데이터 구조
- (컴파일 타임에 한정된 얼랭의 레코드와 대비되는) 폴리모픽 레코드
- 스트릭트 및 레이지한 열거 API
- 경로나 파일시스템 등 스크립팅용 편의성 함수
- 엘릭서 코드를 컴파일하고 테스트할 수 있는 프로젝트 관리 툴
그리고 그 외에도 많이 있습니다.
위에 언급된 기능 대부분에는 자체적인 확장 메커니즘이 있습니다. 예를 들어 Enum
모듈을 봅시다. Enum
모듈은 내장된 레인지, 리스트, 셋 등의 데이터 타입을 열거할 수 있도록 해줍니다.
그 뿐 아니라 모든 개발자는 어떤 데이터 타입에건 Enumerable
프로토콜을 구현하기만 하면 Enum
모듈을 익스텐드해서 해당 데이터 타입에 사용할 수 있습니다. 개발자가 셋, 리스트, 딕셔너리용 개별적 API를 외우는 대신 Enum
API만 알면 열거를 할 수 있게 해주는 매우 간편한 방식입니다.
언어에서 제공하는 프로토콜은 이 외에도 더 많이 있습니다. 데이터 구조를 보기 좋게 출력해주는 Inspect
프로토콜도 있고, 키를 사용해서 키-밸류 데이터에 접근할 수 있는 Access
프로토콜도 있습니다. 엘릭서는 확장이 가능하기 때문에 개발자가 언어와 싸우는 대신 협력해서 개발할 수 있도록 해줍니다.
요약
이 글의 목적은 호환성, 생산성, 확장성이라는 엘릭서 언어 목표를 요약하는 것이었습니다. 우리는 얼랭 VM과 호환성을 유지함으로써 개발자들에게 병행, 분산, 장해 허용 시스템을 만들 수 있는 또다른 툴셋을 제공하고 있습니다.
또한 엘릭서가 얼랭 VM에 무엇을 제공하는지가 이 글에서 명확히 전달되었기를 바랍니다. 구체적으로는 매크로를 통한 메타프로그래밍, 확장성을 위한 폴리모픽 구성요소, 다양한 타입에 대한 확장성 있고 일관적인 표준 라이브러리가 제공됩니다. 여기에는 스트릭트 및 레이지 열거, 유니코드 처리, 테스트 프레임워크 등도 포함됩니다.
엘릭서를 한 번 사용해 보세요! 공식 엘릭서 시작 가이드로 시작하거나, 아니면 사이드바에서 타 학습자료를 확인해볼 수 있습니다.