강한 타입 언어
강한 타입 언어 (Strongly typed language)
Ada는 강한 타입(strongly typed) 언어예요. 이 점이 흥미롭게도 현대적이에요. 정적 타입 함수형 프로그래밍의 성장, 타입 영역의 연구 커뮤니티의 강한 추진, 강한 타입 시스템을 가진 많은 실용적인 언어 덕분에 강한 정적 타입은 프로그래밍 언어 설계에서 인기가 높아지고 있거든요.
출처: 강한 타입 언어 문서
본문
타입이란 무엇인가? (What is a type?)
정적 타입 언어에서 타입은 주로(하지만 전적으로는 아닌) 컴파일 타임 구조예요. 프로그램의 동작에 관한 불변식(invariants)을 강제하는 구조이죠. 불변식은 주어진 타입의 모든 변수에 대해 성립하는 불변 속성이며, 이를 강제하면 데이터 타입의 변수가 유효하지 않은 값을 절대 갖지 않도록 보장할 수 있어요.
타입은 프로그램이 조작하는 객체(객체는 변수 또는 상수)에 대해 추론하는 데 사용돼요. 목표는 객체를 그녀석으로 무엇을 할 수 있는지(즉 허용되는 연산)에 따라 분류하는 것이고, 이렇게 하면 객체 값의 정확성에 대해 추론할 수 있어요.
정수 (Integers)
Ada의 좋은 기능 중 하나는 프로그램의 요구사항(즉 의미 있는 값의 범위)에 기반해 자신만의 정수 타입을 정의할 수 있다는 점이에요. 실제로 Ada가 제공하는 정의 메커니즘은 미리 정의된 정수 타입의 의미 기반을 형성해요. 이런 점에서 "마법 같은" 내장 타입이 없어요. 대부분의 언어와 달리, 틀림없이 매우 우아한 설계죠.
with Ada.Text_IO; use Ada.Text_IO;
procedure Integer_Type_Example is
-- Declare a signed integer type,
-- and give the bounds
type My_Int is range -1 .. 20;
-- ^ High bound
-- ^ Low bound
-- Like variables, type declarations can
-- only appear in declarative regions.
begin
for I in My_Int loop
Put_Line (My_Int'Image (I));
-- ^ 'Image attribute
-- converts a value
-- to a String.
end loop;
end Integer_Type_Example;
이 예시는 부호 있는 정수 타입의 선언과 그걸로 할 수 있는 몇 가지를 보여줘요. Ada의 모든 타입 선언은 type 키워드로 시작해요(단, 태스크 타입 제외). type 뒤에 for 루프에서 쓰는 범위와 많이 닮은 범위가 보이는데, 이것이 타입의 하한·상한 경계를 정의해요. 경계를 포함하는 범위 안의 모든 정수가 그 타입의 유효한 값이에요.
Ada 정수 타입: Ada에서 정수 타입은 기계 표현으로 지정되지 않고 범위로 지정돼요. 그러면 컴파일러가 가장 적절한 표현을 선택해요.
위 예시의 My_Int'Image (I) 표현식도 눈여겨볼 점이에요. Name'Attribute (optional params) 표기법은 Ada에서 애트리뷰트(attribute) 라고 하는 것에 사용돼요. 애트리뷰트는 타입, 값, 또는 다른 프로그램 개체에 대한 내장 연산이에요. ' 기호(ASCII 아포스트로피)로 접근해요.
Ada에는 "내장"으로 사용 가능한 여러 타입이 있고 Integer도 그중 하나예요. 전형적인 프로세서에서 Integer가 이렇게 정의될 수 있어요:
type Integer is
range -(2 ** 31) .. +(2 ** 31 - 1);
**는 지수 연산자이며, Integer의 첫 유효 값은 -2³¹, 마지막 유효 값은 2³¹ - 1이라는 뜻이에요. Ada는 내장 타입 Integer의 범위를 강제하지 않아요. 16비트 타깃용 구현은 -2¹⁵부터 2¹⁵ - 1 범위를 선택할 가능성이 높아요.
연산 의미론 (Operational semantics)
일부 다른 언어와 달리 Ada는 정수에 대한 연산이 오버플로 검사 대상이 되도록 요구해요.
procedure Main is
A : Integer := Integer'Last;
B : Integer;
begin
B := A + 5;
-- This operation will overflow, eg. it
-- will raise an exception at run time.
end Main;
오버플로 검사에는 두 종류가 있어요:
- 기계 수준 오버플로: 연산 결과가 타입의 객체용으로 예약된 저장소에서 표현할 수 있는 최대값을 초과(또는 최소값보다 작음)할 때.
- 타입 수준 오버플로: 연산 결과가 타입에 정의된 범위 밖일 때.
주로 효율성 때문에, 기계 수준 오버플로는 항상 예외를 발생시키지만 타입 수준 오버플로는 대입 같은 특정 경계에서만 검사돼요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
type My_Int is range 1 .. 20;
A : My_Int := 12;
B : My_Int := 15;
M : My_Int := (A + B) / 2;
-- No overflow here, overflow checks
-- are done at specific boundaries.
begin
for I in 1 .. M loop
Put_Line ("Hello, World!");
end loop;
-- Loop body executed 13 times
end Main;
타입 수준 오버플로는 실행의 특정 지점에서만 검사돼요. 위에서 보듯, 중간 계산에서 오버플로가 나도 최종 결과가 오버플로하지 않으면 예외가 발생하지 않을 수 있어요.
부호 없는 타입 (Unsigned types)
Ada에는 부호 없는 정수 타입도 있어요. Ada 용어로 모듈러(modular) 타입이라고 불러요. 이런 명칭이 붙은 이유는 오버플로 시 동작 때문이에요. 마치 모듈로 연산을 적용한 것처럼 "감싸기(wrap around)"만 하죠.
기계 크기의 모듈러 타입(예: 2³² 모듈러스)에서는 부호 없는 타입의 가장 흔한 구현 동작을 흉내 내요. 하지만 Ada의 장점은 모듈러스가 더 일반적이라는 점이에요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
type Mod_Int is mod 2 ** 5;
-- ^ Range is 0 .. 31
A : constant Mod_Int := 20;
B : constant Mod_Int := 15;
M : constant Mod_Int := A + B;
-- No overflow here,
-- M = (20 + 15) mod 32 = 3
begin
for I in 1 .. M loop
Put_Line ("Hello, World!");
end loop;
end Main;
C/C++와 달리 이 감싸기 동작이 Ada 사양으로 보장되기 때문에 이에 의존해 이식 가능한 코드를 구현할 수 있어요. 또 임의의 경계에서 감싸기를 활용할 수 있다는 점이 매우 유용해요. 모듈러스가 2의 거듭제곱일 필요가 없으니까요. 링 버퍼 같은 특정 알고리즘과 자료 구조를 구현하는 데 쓸 수 있어요.
열거 (Enumerations)
열거 타입은 Ada 타입 시스템의 또 다른 장점이에요. C의 enum과 달리 정수가 아니며, 각 새 열거 타입은 다른 열거 타입과 호환되지 않아요. 열거 타입은 더 큰 이산형(discrete) 타입 계열의 일부이며, 나중에 설명할 특정 상황에서 사용할 수 있어요. 이미 본 한 가지 맥락은 case 문장이에요.
with Ada.Text_IO; use Ada.Text_IO;
procedure Enumeration_Example is
type Days is (Monday, Tuesday, Wednesday,
Thursday, Friday,
Saturday, Sunday);
-- An enumeration type
begin
for I in Days loop
case I is
when Saturday .. Sunday =>
Put_Line ("Week end!");
when Monday .. Friday =>
Put_Line ("Hello on "
& Days'Image (I));
-- 'Image attribute, works on
-- enums too
end case;
end loop;
end Enumeration_Example;
열거 타입은 대부분의 언어와 달리 표준 Boolean 타입을 정의하는 데 쓰일 만큼 강력해요:
type Boolean is (False, True);
앞서 언급했듯 Ada의 모든 "내장" 타입은 사용자가 일반적으로 사용할 수 있는 기능으로 정의돼요.
부동소수점 타입 (Floating-point types)
기본 속성 (Basic properties)
대부분의 언어처럼 Ada는 부동소수점 타입을 지원해요. 가장 흔히 쓰는 부동소수점 타입은 Float예요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Floating_Point_Demo is
A : constant Float := 2.5;
begin
Put_Line ("The value of A is "
& Float'Image (A));
end Floating_Point_Demo;
애플리케이션은 A의 값으로 2.5를 표시해요. Ada 언어는 Float의 정밀도(가수부의 십진 자릿수)를 지정하지 않아요. 전형적인 32비트 기계에서는 정밀도가 6이 돼요.
부동소수점 타입에 기대되는 모든 일반 연산을 사용할 수 있어요. 절대값과 지수 연산도 포함돼요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Floating_Point_Operations is
A : Float := 2.5;
begin
A := abs (A - 4.5);
Put_Line ("The value of A is "
& Float'Image (A));
A := A ** 2 + 1.0;
Put_Line ("The value of A is "
& Float'Image (A));
end Floating_Point_Operations;
첫 연산 후 A의 값은 2.0, 두 번째 연산 후에는 5.0이에요.
Float 외에도 Ada 구현은 Long_Float, Long_Long_Float 같은 더 높은 정밀도의 데이터 타입을 제공할 수 있어요. Float처럼 표준은 이 타입들의 정확한 정밀도를 나타내지 않아요. 예를 들어 Long_Float 타입이 Float의 정밀도 이상을 가진다는 것만 보장할 뿐이에요. 특정 정밀도 요구사항이 충족됨을 보장하려면 다음 절에서 볼 것처럼 사용자 정의 부동소수점 타입을 정의할 수 있어요.
부동소수점 타입의 정밀도 (Precision of floating-point types)
Ada는 부동소수점 타입의 정밀도를 십진 자릿수로 지정할 수 있게 해줘요. 이 사용자 정의 타입에 대한 연산은 지정된 정밀도 이상을 갖게 돼요. 단순한 부동소수점 타입 선언의 구문:
type T is digits <number_of_decimal_digits>;
컴파일러는 필요한 정밀도를 지원하는 부동소수점 표현을 선택해요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Custom_Floating_Types is
type T3 is digits 3;
type T15 is digits 15;
type T18 is digits 18;
begin
Put_Line ("T3 requires "
& Integer'Image (T3'Size)
& " bits");
Put_Line ("T15 requires "
& Integer'Image (T15'Size)
& " bits");
Put_Line ("T18 requires "
& Integer'Image (T18'Size)
& " bits");
end Custom_Floating_Types;
이 예시에서 'Size 애트리뷰트는 지정된 데이터 타입에 사용되는 비트 수를 가져오는 데 쓰여요. 실행해 보면 컴파일러가 T3에 32비트, T15에 64비트, T18에 128비트를 할당함을 알 수 있어요. 이는 가수부와 지수부를 모두 포함해요.
데이터 타입에 지정된 자릿수는 부동소수점 변수를 표시할 때 포맷에도 사용돼요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Display_Custom_Floating_Types is
type T3 is digits 3;
type T18 is digits 18;
C1 : constant := 1.0e-4;
A : constant T3 := 1.0 + C1;
B : constant T18 := 1.0 + C1;
begin
Put_Line ("The value of A is "
& T3'Image (A));
Put_Line ("The value of B is "
& T18'Image (B));
end Display_Custom_Floating_Types;
예상대로 애플리케이션은 지정된 정밀도에 따라 변수를 표시해요 (1.00E+00 및 1.00010000000000000E+00).
부동소수점 타입의 범위 (Range of floating-point types)
정밀도 외에도 부동소수점 타입에 범위를 지정할 수 있어요. 구문은 정수 데이터 타입에 사용되는 것과 비슷하며 range 키워드를 사용해요. 이 간단한 예시는 Float 타입을 기반으로 -1.0부터 1.0 사이의 정규화된 범위를 가진 새 부동소수점 타입을 만들어요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Floating_Point_Range is
type T_Norm is new Float range -1.0 .. 1.0;
A : T_Norm;
begin
A := 1.0;
Put_Line ("The value of A is "
& T_Norm'Image (A));
end Floating_Point_Range;
이 타입의 변수가 이 범위 안에 머물도록 하는 건 애플리케이션의 책임이에요. 그렇지 않으면 예외가 발생하죠. 이 예시에서 변수 A에 2.0을 대입하면 Constraint_Error 예외가 발생해요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Floating_Point_Range_Exception is
type T_Norm is new Float range -1.0 .. 1.0;
A : T_Norm;
begin
A := 2.0;
Put_Line ("The value of A is "
& T_Norm'Image (A));
end Floating_Point_Range_Exception;
사용자 정의 부동소수점 타입에도 범위를 지정할 수 있어요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics; use Ada.Numerics;
procedure Custom_Range_Types is
type T6_Inv_Trig is
digits 6 range -Pi / 2.0 .. Pi / 2.0;
begin
null;
end Custom_Range_Types;
이 예시에서는 -π / 2부터 π / 2까지의 범위와 최소 6자리 정밀도를 가진 T6_Inv_Trig 타입을 정의해요. (Pi는 미리 정의된 Ada.Numerics 패키지에 정의돼 있어요.)
강한 타입 지정 (Strong typing)
앞서 언급했듯 Ada는 강한 타입 언어예요. 결과적으로 같은 계열의 서로 다른 타입은 서로 호환되지 않아요. 한 타입의 값은 다른 타입의 변수에 대입될 수 없어요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Illegal_Example is
-- Declare two different floating point types
type Meters is new Float;
type Miles is new Float;
Dist_Imperial : Miles;
-- Declare a constant
Dist_Metric : constant Meters := 1000.0;
begin
-- Not correct: types mismatch
Dist_Imperial := Dist_Metric * 621.371e-6;
Put_Line (Miles'Image (Dist_Imperial));
end Illegal_Example;
이 규칙의 결과로, 일반적인 경우 2 * 3.0 같은 "혼합 모드" 표현식은 컴파일 오류를 일으켜요. C나 Python 같은 언어에서는 이런 표현식이 암묵적 변환으로 유효해져요. Ada에서는 그런 변환이 명시적으로 이루어져야 해요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Conv is
type Meters is new Float;
type Miles is new Float;
Dist_Imperial : Miles;
Dist_Metric : constant Meters := 1000.0;
begin
Dist_Imperial :=
Miles (Dist_Metric) * 621.371e-6;
-- ^^^^^^^^^^^^^^^^^
-- Type conversion, from Meters to Miles
-- Now the code is correct
Put_Line (Miles'Image (Dist_Imperial));
end Conv;
물론 미터를 마일로 변환할 때마다 변환 코드를 매번 쓰고 싶지는 않아요. 이 경우 관용적인 Ada 방식은 타입과 함께 변환 함수를 도입하는 것이에요.
with Ada.Text_IO; use Ada.Text_IO;
procedure Conv is
type Meters is new Float;
type Miles is new Float;
-- Function declaration, like procedure
-- but returns a value.
function To_Miles (M : Meters) return Miles is
-- ^ Return type
begin
return Miles (M) * 621.371e-6;
end To_Miles;
Dist_Imperial : Miles;
Dist_Metric : constant Meters := 1000.0;
begin
Dist_Imperial := To_Miles (Dist_Metric);
Put_Line (Miles'Image (Dist_Imperial));
end Conv;
숫자 코드를 많이 작성한다면 명시적으로 변환을 제공해야 한다는 점이 처음에는 고통스러워 보일 수 있어요. 하지만 이 접근법에는 이점이 있어요. 특히 암묵적 변환이 없다는 점에 의존할 수 있고, 이는 미묘한 오류를 막아줘요.
C에서 암묵적 변환 규칙이 항상 완전히 명확한 것은 아니에요. Ada에서는 코드가 항상 보이는 그대로 정확히 동작해요. 예를 들어:
int a = 3, b = 2;
float f = a / b;
이 코드는 컴파일은 되지만 f의 결과는 1.5가 아니라 1.0이 돼요. 컴파일러가 정수 나눗셈(3 나누기 2)을 생성해 1이 되기 때문이에요. 개발자는 데이터 변환 문제를 알고 적절한 캐스팅을 사용해야 해요:
int a = 3, b = 2;
float f = (float)a / b;
고쳐진 예시에서 컴파일러는 나눗셈을 하기 전에 두 변수를 해당 부동소수점 표현으로 변환해요. 그래서 예상 결과가 나와요.
이 예시는 매우 단순하고, 경험 많은 C 개발자는 더 큰 문제를 만들기 전에 알아차려 고칠 거예요. 하지만 타입 선언이 항상 보이지 않는 더 복잡한 애플리케이션(예: struct의 요소를 참조할 때)에서는 이 상황이 항상 명확하지 않아 소프트웨어 결함으로 빠르게 이어질 수 있고 찾기도 어려울 수 있어요.
반면 Ada 컴파일러는 명시적 변환 없이 부동소수점 변수와 정수 변수를 섞는 코드를 항상 거부해요. C의 잘못된 예시를 기반으로 한 다음 Ada 코드는 컴파일되지 않아요:
procedure Main is
A : Integer := 3;
B : Integer := 2;
F : Float;
begin
F := A / B;
end Main;
문제의 줄은 컴파일러가 받아들이려면 F := Float (A) / Float (B);로 바꿔야 해요.
위 예시처럼 Ada의 강한 타입을 사용해 코드의 불변식을 강제할 수 있어요. Miles와 Meters가 서로 다른 타입이므로, 한쪽 인스턴스를 다른 쪽으로 잘못 변환할 수 없어요.
파생 타입 (Derived types)
Ada에서는 기존 타입에 기반해 새 타입을 만들 수 있어요. 이것은 매우 유용해요. 기존 타입과 같은 속성을 가지되 강한 타입의 이익을 위해 별개의 타입으로 취급되는 타입을 얻을 수 있거든요.
procedure Main is
-- ID card number type,
-- incompatible with Integer.
type Social_Security_Number is new Integer
range 0 .. 999_99_9999;
-- ^ Since a SSN has 9 digits
-- max., and cannot be
-- negative, we enforce
-- a validity constraint.
SSN : Social_Security_Number :=
555_55_5555;
-- ^ You can put underscores as
-- formatting in any number.
I : Integer;
-- The value -1 below will cause a
-- runtime error and a compile time
-- warning with GNAT.
Invalid : Social_Security_Number := -1;
begin
-- Illegal, they have different types:
I := SSN;
-- Likewise illegal:
SSN := I;
-- OK with explicit conversion:
I := Integer (SSN);
-- Likewise OK:
SSN := Social_Security_Number (I);
end Main;
Social_Security 타입은 파생 타입(derived type) 이라고 하고, 그 부모 타입(parent type) 은 Integer예요. 이 예시에서 보듯 파생 스칼라 타입(정수, 부동소수점, 열거 등)을 정의할 때 유효 범위를 다듬을 수 있어요.
열거형의 구문은 range <range> 구문을 사용해요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet is
type Days is (Monday, Tuesday, Wednesday,
Thursday, Friday,
Saturday, Sunday);
type Weekend_Days is new
Days range Saturday .. Sunday;
-- New type, where only Saturday and Sunday
-- are valid literals.
begin
null;
end Greet;
하위 타입 (Subtypes)
점점 보이듯 타입은 유효 값 범위에 제약을 강제하는 데 쓰일 수 있어요. 하지만 때로는 단일 타입 안에서 어떤 값에 제약을 강제하고 싶을 때가 있어요. 이때 하위 타입(subtype)이 역할을 해요. 하위 타입은 새 타입을 도입하지 않아요.
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet is
type Days is (Monday, Tuesday, Wednesday,
Thursday, Friday,
Saturday, Sunday);
-- Declaration of a subtype
subtype Weekend_Days is
Days range Saturday .. Sunday;
-- ^ Constraint of the subtype
M : Days := Sunday;
S : Weekend_Days := M;
-- No error here, Days and Weekend_Days
-- are of the same type.
begin
for I in Days loop
case I is
-- Just like a type, a subtype can
-- be used as a range
when Weekend_Days =>
Put_Line ("Week end!");
when others =>
Put_Line ("Hello on "
& Days'Image (I));
end case;
end loop;
end Greet;
표준 패키지에는 여러 하위 타입이 미리 정의되어 자동으로 사용 가능해요:
subtype Natural is Integer range 0 .. Integer'Last;
subtype Positive is Integer range 1 .. Integer'Last;
한 타입의 하위 타입들은 서로 정적으로 호환되지만, 제약은 런타임에 강제돼요. 하위 타입 제약을 위반하면 예외가 발생해요.
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet is
type Days is (Monday, Tuesday, Wednesday,
Thursday, Friday,
Saturday, Sunday);
subtype Weekend_Days is
Days range Saturday .. Sunday;
Day : Days := Saturday;
Weekend : Weekend_Days;
begin
Weekend := Day;
-- ^ Correct: Same type, subtype
-- constraints are respected
Weekend := Monday;
-- ^ Wrong value for the subtype
-- Compiles, but exception at runtime
end Greet;
타입 별칭으로서의 하위 타입 (Subtypes as type aliases)
앞서 type Miles is new Float처럼 선언해서 새 타입을 만들 수 있다는 걸 봤어요. 또한 알려진 타입에 대한 대체 이름 — 별칭(aliases) — 을 생성하는 타입 별칭을 만들 수도 있어요. 타입 별칭은 타입 동의어(type synonyms) 라고도 불려요.
Ada에서는 새 제약이 없는 하위 타입을 사용해 이를 달성해요. 하지만 이 경우 Ada의 강한 타입 검사의 모든 이점을 얻지는 못해요. 타입 별칭을 사용해 예시를 다시 써 볼게요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Undetected_Imperial_Metric_Error is
-- Declare two type aliases
subtype Meters is Float;
subtype Miles is Float;
Dist_Imperial : Miles;
-- Declare a constant
Dist_Metric : constant Meters := 100.0;
begin
-- No conversion to Miles type required:
Dist_Imperial := (Dist_Metric * 1609.0)
/ 1000.0;
-- Not correct, but undetected:
Dist_Imperial := Dist_Metric;
Put_Line (Miles'Image (Dist_Imperial));
end Undetected_Imperial_Metric_Error;
위 예시에서 Meters와 Miles 둘 다 Float의 하위 타입이라는 사실 덕분에 타입 변환 없이 두 타입의 변수를 섞을 수 있어요. 하지만 이는 위 코드에서 강조된 감지되지 않은 오류에서 보듯 피하고 싶은 온갖 종류의 프로그래밍 실수로 이어질 수 있어요. 그 예시에서 미터 값을 마일 값 저장용 변수에 대입하는 오류는 Meters와 Miles 둘 다 Float의 하위 타입이기 때문에 감지되지 않은 채 남아요. 따라서 위와 같은 경우에는 type X is new Y를 통한 강한 타입 사용을 권장해요.
하지만 타입 별칭이 유용한 상황도 많아요. 예를 들어 여러 문맥에서 부동소수점 타입을 사용하는 애플리케이션에서, 타입에 추가 의미를 나타내거나 긴 변수 이름을 피하기 위해 타입 별칭을 사용할 수 있어요. 예를 들어 이렇게 쓰는 대신:
Paid_Amount, Due_Amount : Float;
이렇게 쓸 수 있어요:
subtype Amount is Float;
Paid, Due : Amount;
C에서는 typedef 선언으로 타입 별칭을 만들 수 있어요. 예를 들어:
typedef float meters;
이것은 위에서 하위 타입으로 본 선언에 대응해요. 다른 프로그래밍 언어도 이 개념을 비슷한 방식으로 포함해요:
- C++:
using meters = float; - Swift:
typealias Meters = Double - Kotlin:
typealias Meters = Double - Haskell:
type Meters = Float
단, Ada의 하위 타입은 새 제약이 없을 때만 타입 별칭에 대응한다는 점에 주의해요. 하위 타입 선언에 새 제약을 추가하면 더 이상 타입 별칭이 아니에요. 예를 들어 다음 선언은 Float의 타입 별칭으로 간주할 수 없어요:
subtype Meters is Float range 0.0 .. 1_000_000.0;
다른 예시를 볼게요:
subtype Degree_Celsius is Float;
subtype Liquid_Water_Temperature is
Degree_Celsius range 0.0 .. 100.0;
subtype Running_Water_Temperature is
Liquid_Water_Temperature;
이 예시에서 Liquid_Water_Temperature는 Degree_Celsius 선언의 일부가 아니었던 새 제약을 추가하므로 Degree_Celsius의 별칭이 아니에요. 하지만 여기에는 두 개의 타입 별칭이 있어요:
Degree_Celsius는Float의 별칭이에요.Running_Water_Temperature는Liquid_Water_Temperature(자체가 제약된 범위를 가졌어도)의 별칭이에요.