Record 모듈
Record 모듈
레코드(record)를 다루고, 정의하고, 가져오는(import) 모듈이에요. 레코드는 단순히 첫 번째 요소가 아톰인 튜플입니다.
iex> Record.is_record({User, "john", 27})
true
이 모듈은 컴파일 타임에 레코드로 작업하기 위한 편의 기능을 제공해요. 컴파일 타임 필드 이름으로 튜플을 조작해서, 튜플의 컴팩트한 구조 위에서 빠른 연산을 제공합니다.
출처: Record
본문
Elixir에서 레코드는 주로 두 상황에서 쓰여요.
- 짧고 내부적인 데이터를 다룰 때
- Erlang 레코드와 인터페이스할 때
매크로 defrecord/3과 defrecordp/3은 레코드를 만들 때, extract/2와 extract_all/1은 Erlang 파일에서 레코드를 추출할 때 쓸 수 있어요.
타입(Types)
record/2 구성(타입스펙에서만 사용 가능)으로 튜플에 타입을 정의할 수 있어요. 레코드 이름은 아톰으로, 필드와 그 타입의 키워드 리스트를 인자로 받습니다.
defmodule MyModule do
require Record
Record.defrecord(:user, name: "john", age: 25)
@type user :: record(:user, name: String.t(), age: integer)
# expands to: "@type user :: {:user, String.t(), integer}"
end
Reflection
레코드 태그와 그 필드는 레코드 정의 매크로의 "Docs" 청크에 메타데이터로 저장됩니다. Code.fetch_docs/1을 호출해서 모듈의 문서를 가져올 수 있어요.
가드(Guards)
is_record(data) (macro)
주어진 data가 레코드인지 확인합니다. guard 절에서 쓸 수 있도록 매크로로 구현되어 있어요.
Record.is_record({User, "john", 27})
#=> true
Record.is_record({})
#=> false
is_record(data, kind) (macro)
주어진 data가 kind 종류의 레코드인지 확인합니다. guard 절에서 쓸 수 있도록 매크로로 구현되어 있어요.
iex> record = {User, "john", 27}
iex> Record.is_record(record, User)
true
타입(Types)
extract_opts()
@type extract_opts() :: [
from: binary(),
from_lib: binary(),
includes: [binary()],
macros: keyword()
]
함수(Functions)
defrecord(name, tag \ nil, kv) (macro)
레코드를 생성·접근·패턴 매칭하기 위한 일련의 매크로를 정의합니다. 생성되는 매크로의 이름은 name(아톰이어야 함)이에요. tag도 아톰이며 레코드의 "태그"(즉 레코드 튜플의 첫 요소)로 쓰입니다. 기본값(nil)이면 name과 같습니다. kv는 새 레코드의 name: default_value 필드 키워드 리스트입니다.
다음 매크로가 생성됩니다.
name/0— 모든 필드에 기본값으로 새 레코드 생성name/1— 주어진 필드·값으로 새 레코드를 만들거나, 주어진 필드의 0부터 시작하는 인덱스를 얻거나, 주어진 레코드를 키워드 리스트로 변환name/2— 주어진 필드·값으로 기존 레코드를 갱신하거나, 주어진 레코드에서 주어진 필드에 접근
이 모든 매크로는 (defmacro로 정의되는) 공개 매크로입니다. 사용법은 "예시" 섹션을 참고하세요.
예시
defmodule User do
require Record
Record.defrecord(:user, name: "meg", age: "25")
end
위 예시에서 arity가 다른 user라는 이름의 매크로 집합이 기저 레코드를 조작하도록 정의됩니다.
# Import the module to make the user macros locally available
import User
# To create records
record = user() #=> {:user, "meg", 25}
record = user(age: 26) #=> {:user, "meg", 26}
# To get a field from the record
user(record, :name) #=> "meg"
# To update the record
user(record, age: 26) #=> {:user, "meg", 26}
# To get the zero-based index of the field in record tuple
# (index 0 is occupied by the record "tag")
user(:name) #=> 1
# Convert a record to a keyword list
user(record) #=> [name: "meg", age: 26]
생성된 매크로는 레코드에 패턴 매칭하고 매칭 중 변수를 바인딩하는 데도 쓸 수 있어요.
record = user() #=> {:user, "meg", 25}
user(name: name) = record
name #=> "meg"
기본적으로 Elixir는 레코드 이름을 튜플의 첫 요소("태그")로 사용합니다. 하지만 아래 예시처럼 레코드를 정의할 때 다른 태그를 지정할 수 있는데, defrecord/3의 두 번째 인자로 Customer를 썼습니다.
defmodule User do
require Record
Record.defrecord(:user, Customer, name: nil)
end
require User
User.user() #=> {Customer, nil}
값에 익명 함수가 있는 추출 레코드 정의하기
레코드가 기본값에 익명 함수를 정의하면 ArgumentError가 발생합니다. 이는 기본값에 익명 함수를 쓰는 Erlang 라이브러리에서 레코드를 추출한 뒤 정의할 때 의도치 않게 일어날 수 있어요.
Record.defrecord(:my_rec, Record.extract(...))
** (ArgumentError) invalid value for record field fun_field,
cannot escape #Function<12.90072148/2 in :erl_eval.expr/5>.
이 오류를 우회하려면 필드를 자신만의 &M.f/a 함수로 재정의하면 됩니다.
defmodule MyRec do
require Record
Record.defrecord(:my_rec, Record.extract(...) |> Keyword.merge(fun_field: &__MODULE__.foo/2))
def foo(bar, baz), do: IO.inspect({bar, baz})
end
defrecordp(name, tag \ nil, kv) (macro)
defrecord/3와 같지만 비공개(private) 매크로를 생성합니다.
extract(name, opts)
@spec extract(name :: atom(), extract_opts()) :: keyword()
Erlang 파일에서 레코드 정보를 추출합니다. 필드를 튜플 리스트로 담은 quoted expression을 반환합니다. 추출되는 레코드의 이름인 name은 컴파일 타임에 아톰일 것이 기대됩니다.
옵션
이 함수는 다음 옵션 중 하나를 요구하며, 서로 배타적입니다(즉 같은 호출에서 하나만 쓸 수 있어요).
:from— (파일 경로를 나타내는 바이너리) 추출할 레코드 정의가 있는 Erlang 파일의 경로. 이 옵션으로 이 함수는 Erlang 모듈의-include속성이 쓰는 것과 같은 경로 조회를 사용합니다.:from_lib— (파일 경로를 나타내는 바이너리) 추출할 레코드 정의가 있는 Erlang 파일의 경로. 이 옵션으로 이 함수는 Erlang 모듈의-include_lib속성이 쓰는 것과 같은 경로 조회를 사용합니다.
추가로 다음의 선택적이고 배타적이지 않은 옵션도 받습니다.
:includes— (바이너리 디렉터리 리스트) 추출되는 레코드가 상대 include에 의존할 때, 그 상대 include가 존재하는 디렉터리를 지정하게 해 줍니다.:macros— (매크로 이름·값의 키워드 리스트) 추출되는 레코드가 매크로 값에 의존할 때, 그 매크로 값들을 설정하게 해 줍니다.
이 옵션들은 컴파일 타임에 리터럴(바이너리 값 포함)일 것이 기대됩니다.
예시
iex> Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
[
size: :undefined,
type: :undefined,
access: :undefined,
atime: :undefined,
mtime: :undefined,
ctime: :undefined,
mode: :undefined,
links: :undefined,
major_device: :undefined,
minor_device: :undefined,
inode: :undefined,
uid: :undefined,
gid: :undefined
]
extract_all(opts)
@spec extract_all(extract_opts()) :: [{name :: atom(), keyword()}]
Erlang 파일에서 모든 레코드 정보를 추출합니다. record_name이 추출된 레코드 이름이고 fields가 그 레코드의 필드를 나타내는 {field, value} 튜플 리스트인 {record_name, fields} 튜플의 키워드 리스트를 반환합니다.
옵션
Record.extract/2에 나열된 것과 같은 옵션을 받습니다.
더 알아보기
defrecord/3— 레코드 정의 매크로Kernel.defstruct/1— 구조체 정의 (레코드보다 권장)Code.fetch_docs/1— 모듈 문서 가져오기Struct— 구조체 관련 함수