alias, require, import, 그리고 use
alias, require, import, 그리고 use
코드를 재사용하다 보면 모듈 이름을 줄이고 싶고, 매크로를 쓰려면 모듈을 로드하고, 함수를 짧게 부르고 싶고, 다른 모듈의 코드를 주입받고 싶어질 때가 있어요. Elixir는 이런 일을 위해 세 가지 지시자(directive)와 하나의 매크로를 제공해요. 이번 장에서 그 네 가지를 하나씩 뜯어볼게요.
출처: Elixir 공식 가이드
본문
소프트웨어 재사용을 돕기 위해 Elixir는 세 가지 지시자(alias, require, import)와 use라는 매크로를 제공해요. 요약하면 이렇습니다.
# 모듈에 별명을 붙여서 Foo.Bar 대신 Bar로 호출할 수 있게 한다
alias Foo.Bar, as: Bar
# 매크로를 쓰기 위해 해당 모듈을 로드한다
require Foo
# Foo의 함수를 `Foo.` 접두사 없이 호출할 수 있게 import 한다
import Foo
# 확장 지점으로서 Foo에 정의된 커스텀 코드를 호출한다
use Foo
각각을 자세히 살펴볼게요. 처음 세 개는 어휘적 유효 범위(lexical scope) 를 가지기 때문에 지시자(directive)라고 부르고, use는 사용된 모듈이 코드를 주입할 수 있게 하는 흔한 확장 지점(extension point)이라는 점만 먼저 기억해 두면 좋아요.
alias
alias는 어떤 모듈 이름에든 별명을 붙일 수 있게 해줘요.
어떤 모듈이 Math.List에 구현된 특화된 리스트를 쓴다고 상상해 볼게요. alias 지시자를 쓰면 모듈 정의 안에서 Math.List를 그냥 List로 가리킬 수 있어요.
defmodule Stats do
alias Math.List, as: List
# 나머지 모듈 정의에서 List는 Math.List로 확장된다.
end
원래의 List는 여전히 Stats 안에서 완전한 이름인 Elixir.List으로 접근할 수 있어요.
Elixir에 정의된 모든 모듈은
Elixir.String처럼 메인Elixir네임스페이스 안에 정의돼요. 다만 편의상 참조할 때Elixir.을 생략할 수 있어요.
별명(alias)은 자주 단축어를 만들 때 쓰여요. 사실 :as 옵션 없이 alias를 호출하면 별명이 자동으로 모듈 이름의 마지막 부분으로 설정돼요. 예를 들어:
alias Math.List
이건 다음 코드와 같아요.
alias Math.List, as: List
alias는 어휘적 유효 범위를 가지므로 특정 함수 안에서만 별명을 설정할 수도 있어요.
defmodule Math do
def plus(a, b) do
alias Math.List
# ...
end
def minus(a, b) do
# ...
end
end
위 예시에서 alias는 함수 plus/2 안에서 호출됐으므로 별명은 plus/2 안에서만 유효해요. minus/2는 전혀 영향을 받지 않죠.
require
Elixir는 코드를 생성하는 코드, 즉 메타프로그래밍을 위한 매커니즘으로 매크로(macro)를 제공해요. 매크로는 컴파일 시점에 확장돼요.
모듈의 공개 함수는 어디서든 쓸 수 있지만, 매크로를 쓰려면 그 매크로가 정의된 모듈을 require로 로드해서 "동의(opt-in)"해야 해요.
iex> Integer.is_odd(3)
** (UndefinedFunctionError) function Integer.is_odd/1 is undefined or private. However, there is a macro with the same name and arity. Be sure to require Integer if you intend to invoke this macro
(elixir) Integer.is_odd(3)
iex> require Integer
Integer
iex> Integer.is_odd(3)
true
Elixir에서 Integer.is_odd/1는 가드(guard)로 쓸 수 있도록 매크로로 정의돼 있어요. 그래서 Integer.is_odd/1을 호출하려면 먼저 Integer 모듈을 require해야 하죠.
alias 지시자와 마찬가지로 require도 어휘적 유효 범위를 가져요. 매크로에 대해서는 뒤의 장에서 더 자세히 다룰게요.
import
완전한 이름을 쓰지 않고 다른 모듈의 함수나 매크로에 접근하고 싶을 때 import를 써요. 다만 공개 함수만 import할 수 있어요. 비공개 함수는 외부에서 접근할 수 없으니까요.
예를 들어 List 모듈의 duplicate/2 함수를 여러 번 쓰고 싶다면 이렇게 import할 수 있어요.
iex> import List, only: [duplicate: 2]
List
iex> duplicate(:ok, 3)
[:ok, :ok, :ok]
List에서 duplicate(인자 개수 2) 함수만 import했어요. :only는 선택 사항이지만, 특정 모듈의 모든 함수를 현재 범위로 가져오는 것을 피하기 위해 쓰는 걸 권장해요. :except 옵션으로 특정 함수 목록만 빼고 전부 import할 수도 있어요.
import도 어휘적 유효 범위를 가져요. 즉, 함수 정의 안에서 특정 매크로나 함수를 import할 수 있다는 뜻이에요.
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end
위 예시에서 가져온 List.duplicate/2는 그 특정 함수 안에서만 보여요. duplicate/2는 그 모듈의 다른 어떤 함수에서도(물론 다른 모듈에서도) 쓸 수 없어요.
import는 프레임워크나 라이브러리가 추상화를 만들 때 유용할 수 있지만, 개발자라면 자신의 코드베이스에서는 일반적으로 alias를 import보다 선호해요. alias가 호출되는 함수의 출처를 더 명확하게 해주거든요.
use
use 매크로는 흔히 확장 지점으로 쓰여요. 모듈 FooBar를 use하면 그 모듈이 현재 모듈에 자기 자신이나 다른 모듈을 import하고, 새 함수를 정의하고, 모듈 상태를 설정하는 등 어떤 코드든 주입할 수 있게 된다는 뜻이에요.
예를 들어 ExUnit 프레임워크로 테스트를 작성할 때는 ExUnit.Case 모듈을 use해야 해요.
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
그 뒤에서 use는 주어진 모듈을 require한 다음 그 모듈의 __using__/1 콜백을 호출해서, 그 모듈이 현재 컨텍스트에 코드를 주입할 수 있게 해줘요. 어떤 모듈들(위의 ExUnit.Case뿐 아니라 Supervisor, GenServer도)은 이 메커니즘으로 여러분의 모듈에, 여러분이 오버라이드하거나 보완할 기본 동작을 채워 넣어요.
일반적으로 다음 모듈은:
defmodule Example do
use Feature, option: :value
end
이렇게 컴파일돼요.
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
use는 어떤 코드든 실행할 수 있게 하므로, 문서를 읽지 않고는 모듈을 use할 때의 부수 효과를 알 수 없어요. 그래서 use는 신중하게, 정말 필요할 때만 써야 해요. import나 alias로 충분한 곳에 use를 쓰지 마세요.
여러 모듈을 한 번에 alias/import/require/use 하기 (Multi alias/import/require/use)
한 번에 여러 모듈을 alias하거나 import, require, use할 수 있어요. 특히 Elixir 애플리케이션을 만들 때 흔한 모듈 중첩이 시작되면 아주 유용하죠. 예를 들어 모든 모듈이 MyApp 아래에 중첩된 애플리케이션이 있다면, MyApp.Foo, MyApp.Bar, MyApp.Baz를 한 번에 이렇게 alias할 수 있어요.
alias MyApp.{Foo, Bar, Baz}
이것으로 Elixir 모듈에 대한 여정을 마칠게요.
더 알아보기
- Modules and functions (이전 장)
- Module attributes (다음 장)
- 매크로 (Elixir 공식 가이드)