레코드

레코드 (Records)

레코드(record)는 고정된 개수의 요소를 저장하는 데이터 구조예요. 이름 붙은 필드(named field)를 가지며, C 언어의 struct와 비슷합니다. 레코드 표현식은 컴파일 과정에서 튜플 표현식으로 변환돼요.

더 많은 예시는 Programming Examples에서 볼 수 있습니다.

출처: Erlang 공식 문서 - Records

레코드 정의 (Defining Records)

레코드 정의는 레코드의 이름과 그 뒤를 따르는 레코드 필드 이름들로 이루어져요. 레코드와 필드 이름은 반드시 원자(atom)여야 합니다. 각 필드에는 선택적으로 기본값(default value)을 줄 수 있고, 기본값을 주지 않으면 undefined가 사용됩니다.

-record(Name, {Field1 [= Expr1],
               ...
               FieldN [= ExprN]}).

필드의 기본값은 임의의 표현식이 될 수 있지만, 변수를 사용해서는 안 됩니다.

레코드 정의는 모듈의 속성(attribute)과 함수 선언 어디에든 둘 수 있지만, 그 레코드를 사용하는 코드보다 먼저 와야 합니다.

레코드를 여러 모듈에서 쓴다면 레코드 정의를 include 파일에 두는 걸 권장해요.

Change {: .info }

Erlang/OTP 26부터 이 섹션에서 설명하는 문법으로 에를랭 셸 안에서도 레코드를 정의할 수 있게 됐어요. 그 이전 릴리스에서는 셸 내장 함수 m:shellrd/2를 사용해야 했습니다.

레코드 생성 (Creating Records)

다음 표현식은 각 필드 FieldI의 값이 해당 표현식 ExprI를 평가한 값인 새 Name 레코드를 만들어요:

#Name{Field1=Expr1, ..., FieldK=ExprK}

필드는 레코드 정의에서의 순서와 다르게 아무 순서로나 쓸 수 있고, 생략할 수도 있어요. 생략된 필드에는 각각의 기본값이 대신 들어갑니다.

여러 필드에 같은 값을 지정하고 싶다면 다음과 같은 구성도 쓸 수 있어요:

#Name{Field1=Expr1, ..., FieldK=ExprK, _=ExprL}

이 경우 생략된 필드는 기본값 대신 ExprL을 평가한 값을 갖게 됩니다. 이 기능은 주로 ETS와 Mnesia의 매칭 함수용 패턴을 만들 때 쓰기 위한 거예요.

예시:

-record(person, {name, phone, address}).

lookup(Name, Tab) ->
    ets:match_object(Tab, #person{name=Name, _='_'}).

레코드 필드 접근 (Accessing Record Fields)

Expr#Name.Field

지정한 필드의 값을 반환해요. ExprName 레코드로 평가되어야 합니다.

예시:

-record(person, {name, phone, address}).

get_person_name(Person) ->
    Person#person.name.

다음 표현식은 레코드의 튜플 표현에서 지정된 필드의 위치를 반환해요:

#Name.Field

예시:

-record(person, {name, phone, address}).

lookup(Name, List) ->
    lists:keyfind(Name, #person.name, List).

레코드 갱신 (Updating Records)

Expr#Name{Field1=Expr1, ..., FieldK=ExprK}

ExprName 레코드로 평가되어야 해요. 이 레코드의 복사본이 반환되는데, 각 지정 필드 FieldI의 값이 해당 표현식 ExprI를 평가한 값으로 바뀝니다. 나머지 필드는 기존 값을 그대로 유지해요.

가드 안의 레코드 (Records in Guards)

레코드 표현식은 튜플 표현식으로 확장되기 때문에, 레코드 생성과 레코드 필드 접근은 가드 안에서도 허용돼요. 다만 (필드를 초기화하는) 모든 하위 표현식 역시 유효한 가드 표현식이어야 합니다.

예시:

handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
    ...

handle(Msg, State) when State#state.running =:= true ->
    ...

또한 타입 검사 BIF인 is_record(Term, RecordTag)도 있어요.

예시:

is_person(P) when is_record(P, person) ->
    true;
is_person(_P) ->
    false.

패턴 안의 레코드 (Records in Patterns)

특정 레코드에 매칭되는 패턴은 레코드를 생성하는 방식과 똑같이 만듭니다:

#Name{Field1=Expr1, ..., FieldK=ExprK}

이 경우 Expr1 ... ExprK 중 하나 이상이 아직 바인딩되지 않은 변수일 수 있어요.

중첩 레코드 (Nested Records)

다음과 같은 레코드 정의가 있다고 가정해 볼게요:

-record(nrec0, {name = "nested0"}).
-record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
-record(nrec2, {name = "nested2", nrec1=#nrec1{}}).

N2 = #nrec2{},

중첩 레코드에 접근하거나 갱신할 때 괄호 없이 쓸 수 있어요:

"nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
    N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},

이는 다음 표현식과 동등합니다:

"nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},

Change {: .info }

Erlang/OTP R14 이전에는 중첩 레코드에 접근하거나 갱신할 때 괄호가 필요했어요.

레코드의 내부 표현 (Internal Representation of Records)

레코드 표현식은 컴파일 과정에서 튜플 표현식으로 변환됩니다. 다음과 같이 정의된 레코드:

-record(Name, {Field1, ..., FieldN}).

는 내부적으로 튜플:

{Name, Value1, ..., ValueN}

로 표현돼요. 여기서 각 ValueIFieldI의 기본값입니다.

레코드를 사용하는 각 모듈에는 컴파일 과정에서 레코드에 대한 정보를 얻는 의사 함수(pseudo function)가 추가됩니다:

record_info(fields, Record) -> [Field]
record_info(size, Record) -> Size

Size는 튜플 표현의 크기, 즉 필드 개수보다 하나 큰 값이에요.

더 알아보기 (Learn more)