Record 모듈

Record 모듈

레코드(record)를 다루고, 정의하고, 가져오는(import) 모듈이에요. 레코드는 단순히 첫 번째 요소가 아톰인 튜플입니다.

iex> Record.is_record({User, "john", 27})
true

이 모듈은 컴파일 타임에 레코드로 작업하기 위한 편의 기능을 제공해요. 컴파일 타임 필드 이름으로 튜플을 조작해서, 튜플의 컴팩트한 구조 위에서 빠른 연산을 제공합니다.

출처: Record

본문

Elixir에서 레코드는 주로 두 상황에서 쓰여요.

  • 짧고 내부적인 데이터를 다룰 때
  • Erlang 레코드와 인터페이스할 때

매크로 defrecord/3defrecordp/3은 레코드를 만들 때, extract/2extract_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)

주어진 datakind 종류의 레코드인지 확인합니다. 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에 나열된 것과 같은 옵션을 받습니다.

더 알아보기