레코드

레코드 (Records)

지금까지 만난 모든 타입은 값이 분해될 수 없는 타입이었어요. 각 인스턴스가 단일 데이터 조각을 나타냈죠. 이제 첫 번째 합성(composite) 타입 종류인 레코드(record) 를 볼 거예요. 레코드는 다른 타입의 인스턴스들로 값을 구성할 수 있게 해줘요. 각 인스턴스에는 이름이 주어지고, 이름과 특정 타입의 인스턴스의 쌍을 필드(field) 또는 컴포넌트(component) 라고 해요.

출처: 레코드 문서

본문

레코드 타입 선언 (Record type declaration)

간단한 레코드 선언 예시:

type Date is record
   --  The following declarations are
   --  components of the record
   Day   : Integer range 1 .. 31;
   Month : Months;
   --  You can add custom constraints
   --  on fields
   Year  : Integer range 1 .. 3000;
end record;

필드는 변수 선언과 아주 비슷해 보이지만, 레코드 정의 안에 있다는 점이 달라요. 그리고 변수 선언과 마찬가지로 필드의 하위 타입을 지정할 때 추가 제약을 지정할 수 있어요.

type Date is record
   Day   : Integer range 1 .. 31;
   Month : Months := January;
   --  This component has a default value
   Year  : Integer range 1 .. 3000 := 2012;
   --                                 ^^^^
   --                              Default value
end record;

레코드 컴포넌트는 기본값을 가질 수 있어요. 레코드 타입의 변수가 선언되면 기본 초기화가 있는 필드는 자동으로 이 값으로 설정돼요. 그 값은 컴포넌트 타입의 어떤 표현식이든 될 수 있고 런타임에 계산될 수도 있어요.

이 장의 나머지 절에서는 레코드 타입을 사용하는 방법을 볼 거예요. 레코드에 대해 더 자세히는 다른 장에서 다룰게요.

애그리게이트 (Aggregates)

--  Positional components
Ada_Birthday    : Date := (10, December, 1815);

--  Named components
Leap_Day_2020   : Date := (Day   => 29,
                           Month => February,
                           Year  => 2020);
--                         ^ By name

레코드는 위에 나온 것처럼 값을 표현하는 편리한 표기법을 가지고 있어요. 이 표기법을 애그리게이트(aggregate) 표기법 이라고 하고, 리터럴을 애그리게이트 라고 해요. 이것들은 과정 전반에 걸쳐 볼 다양한 문맥에서 사용될 수 있으며, 그중 하나가 레코드 초기화예요.

애그리게이트는 쉼표로 구분되고 괄호로 감싼 값들의 목록이에요. 레코드 값이 기대되는 어떤 문맥에서든 허용돼요.

컴포넌트 값은 Ada_Birthday 예시처럼 위치로 지정하거나, Leap_Day_2020처럼 이름으로 지정할 수 있어요. 위치와 이름 값의 혼합은 허용되지만, 이름 뒤에 위치 표기법은 사용할 수 없어요.

컴포넌트 선택 (Component selection)

레코드 인스턴스의 컴포넌트에 접근하려면 컴포넌트 선택(component selection) 이라는 연산을 사용해요. 이것은 점 표기법으로 이루어져요. 예를 들어 위 Date 레코드 타입의 변수 Some_Day를 선언했다면 Some_Day.Year라고 써서 Year 컴포넌트에 접근할 수 있어요.

예시를 볼게요:

with Ada.Text_IO; use Ada.Text_IO;

procedure Record_Selection is

   type Months is
     (January, February, March, April,
      May, June, July, August, September,
      October, November, December);

   type Date is record
      Day   : Integer range 1 .. 31;
      Month : Months;
      Year  : Integer range 1 .. 3000 := 2032;
   end record;

   procedure Display_Date (D : Date) is
   begin
      Put_Line ("Day:" & Integer'Image (D.Day)
                & ", Month: "
                & Months'Image (D.Month)
                & ", Year:"
                & Integer'Image (D.Year));
   end Display_Date;

   Some_Day : Date := (1, January, 2000);

begin
   Display_Date (Some_Day);

   Put_Line ("Changing year...");
   Some_Day.Year := 2001;

   Display_Date (Some_Day);
end Record_Selection;

이 예시에서 D.YearSome_Day.Year 같은 표현식에서 점 표기법을 사용해 그 컴포넌트에 저장된 정보를 접근하고, 대입에서 그 정보를 수정할 수 있음을 볼 수 있어요. 구체적으로, Put_Line 호출에서 D.Year를 사용하면 그 컴포넌트에 저장된 정보를 검색하는 것이고, Some_Day.Year := 2001이라고 쓰면 Some_DayYear 컴포넌트에 이전에 저장된 정보를 덮어쓰는 것이에요.

이름 변경 (Renaming)

이전 장에서 서브프로그램패키지 이름 변경을 다뤘어요. 레코드 컴포넌트도 이름을 바꿀 수 있어요. 점 표기법으로 전체 컴포넌트 선택을 쓰는 대신, 같은 컴포넌트에 접근할 수 있는 별칭을 선언할 수 있어요. 이는 예를 들어 서브프로그램 구현을 단순화하는 데 유용해요.

변수 선언에서 renames 키워드를 사용해 레코드 컴포넌트의 이름을 바꿀 수 있어요. 예를 들어:

Some_Day : Date;
Y        : Integer renames Some_Day.Year;

여기서 Y는 별칭이어서, Y를 쓸 때마다 실제로는 Some_DayYear 컴포넌트를 사용하는 것이에요.

완전한 예시를 볼게요:

package Dates is

   type Months is
     (January, February, March, April,
      May, June, July, August, September,
      October, November, December);

   type Date is record
      Day   : Integer range 1 .. 31;
      Month : Months;
      Year  : Integer range 1 .. 3000 := 2032;
   end record;

   procedure Increase_Month
    (Some_Day : in out Date);

   procedure Display_Month
     (Some_Day : Date);

end Dates;

with Ada.Text_IO; use Ada.Text_IO;

package body Dates is

   procedure Increase_Month
     (Some_Day : in out Date)
   is
      --  Renaming components from
      --  the Date record
      M : Months  renames Some_Day.Month;
      Y : Integer renames Some_Day.Year;

      --  Renaming function (for Months
      --  enumeration)
      function Next (M : Months)
                     return Months
        renames Months'Succ;
   begin
      if M = December then
         M := January;
         Y := Y + 1;
      else
         M := Next (M);
      end if;
   end Increase_Month;

   procedure Display_Month
     (Some_Day : Date)
   is
      --  Renaming components from
      --  the Date record
      M : Months  renames Some_Day.Month;
      Y : Integer renames Some_Day.Year;
   begin
      Put_Line ("Month: "
                & Months'Image (M)
                & ", Year:"
                & Integer'Image (Y));
   end Display_Month;

end Dates;

with Ada.Text_IO; use Ada.Text_IO;
with Dates;       use Dates;

procedure Main is
   D : Date := (1, January, 2000);
begin
   Display_Month (D);

   Put_Line ("Increasing month...");
   Increase_Month (D);

   Display_Month (D);
end Main;

Increase_Month 프로시저 구현에서 Date 레코드의 두 컴포넌트에 이름 변경을 적용해요. 그리고 다음 연산에서 Some_Day.MonthSome_Day.Year를 직접 사용하는 대신, 이름 변경된 버전 MY를 사용해요.

위 예시에서 다음 달을 주는 함수 Months'SuccNext로 이름을 바꾼다는 점에 주의하세요.

더 알아보기 (Learn more)