Elixir – 14: Module attributes
Elixir Tutorial 시리즈입니다. 거의 대부분은 튜토리얼의 한글 번역에 가깝습니다만, 생략되거나 추가로 주석을 달거나 하는 부분이 많습니다. 원문은 최하단의 링크를 참고하세요.
Elixir – 14: Module attributes
모듈 속성은 Elixir에서 크게 3가지 목적을 가지고 있습니다.
- 모듈에 대한 설명을 제공하며, 이 정보를 사용자 또는 VM이 사용하기도 합니다.
- 상수처럼 동작합니다.
- 컴파일 시간에 모듈의 임시 저장소로 사용됩니다.
그럼 하나씩 살펴봅시다.
어노테이션(Annotation)으로서
Elixir는 Erlang의 모듈 속성의 개념을 가져왔습니다.
defmodule MyServer do
@vsn 2
end
이 예제에서는 모듈에서 명시적으로 버전을 속성으로 정의했습니다. @vsn은 Erlang VM에서 코드의 버전을 확인하고 갱신이 필요하다면 리로딩하는 용도로 사용됩니다. 아무런 정보가 없다면 모듈 함수들의 MD5 체크섬을 사용하게 됩니다.
Elxir는 유용한 예약 속성을 가지고 있습니다. 여기에 자주 쓰이는 몇몇을 소개합니다.
@moduledoc– 현재 모듈에 대한 문서를 제공합니다.@doc– 그 뒤에 오는 함수나 매크로의 문서를 제공합니다.@behaviour– OTP나 사용자 정의 행동을 식별하기 위해서 사용합니다.@before_compile– 모듈을 컴파일하기 전에 실행할 훅을 제공합니다. 이를 통해 컴파일 이전에 모듈 속에 함수를 주입할 수 있습니다.
@moduledoc와 @doc는 자주 사용되는 속성은 아닙니다만 자주 사용하기를 권장합니다. Elixir는 문서를 1급 객체로서 다루며 많은 함수들에게 함수에 접근할 수 있도록 하고 있습니다.
그럼 이제 이전 장에서 정의했던 Math 모듈로 돌아가서 몇몇 설명을 추가하고 math.ex 파일에 저장해봅시다.
defmodule Math do
@moduledoc """
Provides math-related functions.
## Examples
iex> Math.sum(1, 2)
3
"""
@doc """
Calculates the sum of two numbers.
"""
def sum(a, b), do: a + b
end
Elixir는 heredoc에서 마크다운을 사용하여 읽기 쉬운 문서를 작성하게 해줍니다. heredoc은 여러 줄의 문자열이며, 시작과 종료를 “””으로 나타내어 내부의 포맷을 유지합니다. 이를 통해 컴파일된 어떤 모듈에서든 IEX에서 직접 문서에 접근할 수 있습니다.
$ elixirc math.ex
$ iex
iex> h Math # Access the docs for the module Math
...
iex> h Math.sum # Access the docs for the sum function
...
문서를 HTML 페이지로 생성하기 위한 ExDoc도 제공하고 있습니다.
Module에서 지원되는 모든 속성에 대한 목록을 확인할 수 있습니다. Elixir는 typespecs을 정의할 때에도 속성을 사용합니다.
이 절에서는 내장 속성을 설명했지만, 속성은 개발자에 의해서 사용되거나 어떤 동작을 위해서 확장될 수도 있습니다.
상수(Constants)로서
Elixir 개발자는 상수로서 모듈 속성을 자주 사용하게 됩니다.
defmodule MyServer do
@initial_state %{host: "147.0.0.1", port: 3456}
IO.inspect @initial_state
end
Note: Erlang과는 다르게 사용자 정의 속성은 모듈에 저장되지 않습니다. 그 값은 기본적으로 컴파일 시간에만 존재합니다. 개발자는 이러한 속성의 동작을 보다 Erlang에 가깝게 만들기 위해
Module.register_attribute/3을 사용할 수 있습니다.
정의되지 않은 속성에 접근하면 경고를 출력합니다.
defmodule MyServer do
@unknown
end
warning: undefined module attribute @unknown, please remove access to @unknown or explicitly set it to nil before access
마지막으로 속성은 함수에서 사용할 수 있습니다.
defmodule MyServer do
@my_data 14
def first_data, do: @my_data
@my_data 13
def second_data, do: @my_data
end
MyServer.first_data #=> 14
MyServer.second_data #=> 13
함수에서 사용하는 값은 속성이 가지고 있는 현재의 값의 스냅샷이라는 점을 기억하세요. 다시 말하자면 값은 컴파일 시간에 읽어진다는 의미입니다. 앞으로 설명하겠지만 이 방식이 저장소로서의 속성을 좀 더 유용하게 만듭니다.
임시 저장소로서
Elixir 조직의 프로젝트중 하나로 Plug가 있습니다만, 이는 Elixir로 웹 라이브러리나 프레임워크를 만들기 위한 공통 라이브러리입니다.
Plug 라이브러리는 개발자에게 자신만의 플러그를 만들어서 서버에 올릴 수 있도록 해줍니다.
defmodule MyPlug do
use Plug.Builder
plug :set_header
plug :send_ok
def set_header(conn, _opts) do
put_resp_header(conn, "x-header", "set")
end
def send_ok(conn, _opts) do
send(conn, 200, "ok")
end
end
IO.puts "Running MyPlug with Cowboy on http://localhost:4000"
Plug.Adapters.Cowboy.http MyPlug, []
이 예제에서는 plug/1 매크로를 사용하여 웹 요청이 있었을 때에 실행할 함수들을 연결하고 있습니다. 내부적으로 plug/1를 호출할 때마다 Plug 라이브러리는 주어진 매개변수들을 @plugs에 저장합니다. 모듈이 컴파일되기 직전에 Plug는 HTTP 요청을 처리하는 함수(call/2)를 정의하는 콜백을 실행합니다. 이 함수는 모든 플러그의 @plugs에서 순서대로 실행됩니다.
이 코드들의 동작을 이해하기 위해서는 매크로에 대한 이해가 필요하므로 이 패턴을 메타 프로그래밍 가이드에서 다시 살펴볼 것입니다. 돌아와서 여기에서 중요한 부분은 어떻게 모듈 속성을 저장소로 사용하여 개발자에게 DSL을 만들 수 있도록 도와주냐는 점입니다.
모듈 속성을 어떻게 어노테이션과 저장소로 활용하는지 ExUnit 프레임워크의 다른 예제를 살펴보죠.
defmodule MyTest do
use ExUnit.Case
@tag :external
test "contacts external service" do
# ...
end
end
ExUnit의 태그들은 테스트를 설명하는 용도로 사용됩니다. 또는 테스트를 필터링하기 위한 용도로도 사용됩니다. 예를 들어, 외부 테스트가 너무 느리고, 다른 서비스에 의존성이 높기 때문에 로컬에서는 외부 테스트를 제외하고 실행할 수 있다는 의미입니다.
