본문 바로가기
프로그래밍언어론

[프로그래밍언어론] 27. 자료형(Data Types)

by 파스텔코랄 2023. 10. 17.

자료형(Data Types)

  • 데이터 타입은 동일한 유형의 값(value)과 조작할 값에 적용할 수 있는 연산자(operation) 집합이다.
    • 동종(homogeneous) : 같거나 유사한 종류(↔ 이종).
    • 값 + 연산자 : 데이터 타입은 값에 관한 것뿐만 아니라 연산도 포함된다.
      • 예) 정수, 문자열, 배열에 대해 서로 다른 연산이 필요하다.

 


 

타입 시스템(Type System)

  • 프로그래밍 언어에는 데이터 유형을 관리하기 위한 자체 타입 시스템(정보 및 규칙)이 있다.
  • 타입 시스템은 일반적으로 다음으로 구성된다.
    • 미리 정의된 타입의 세트
    • 새로운 타입의 정의를 지원하는 메커니즘
    • 동등성 규칙(equivalence rules), 호환성 규칙(compatibility), 타입 추론(type inference)과 같은 타입을 제어하는 메커니즘.

 


 

표시 가능(Denotable), 표현 가능(Expressible), 저장 가능(Storable)

  • 표시 가능(Denotable) : 우리가 이름을 붙일 수 있는 경우
    • 변수(이름), 함수(이름)
  • 표현 가능(Expressible) : 복잡한 표현식에서 얻을 수 있는 경우
    • 숫자, 문자열, 심지어 C의 메모리 위치까지 표현식에 나타날 수 있다.
  • 저장 가능(Storable) : 변수에 저장할 수 있는 경우
    • 변수 vs 함수 : 함수의 코드 조각이 디스크에 저장되어 있지만 프로그램에서 업데이트할 수는 없다.

 


 

정적 및 동적 타입 검사

  • 동적 타입 검사 : 런타임 중에 타입 제약 조건을 검사
  • 정적 타입 검사 : 컴파일 시간에 타입 제약 조건을 검사
    • 런타임 타입 검사가 없다. : 실행이 더 효율적이다.
    • 정적 타입 검사 설계는 더 복잡하고 컴파일 시간도 더 오래 걸린다.
      • 그러나 컴파일은 몇 번만 발생하는 반면 실행은 자주 발생한다.

 

  • 정적 타입 검사에는 보수적인 타입 제약 조건이 필요히다.
    • 런타임 시 발생하지 않는 일부 오류를 보고한다.
  • 예) 예제 코드에서 x = "PL"은 타입 제약 조건을 위반한다.
    • 그러나 이 코드는 런타임 중에 실행되지 않는다.
  • 프로그램이 타입 오류를 발생시키는지 여부를 결정하는 것은 결정할 수 없기 때문이다.
int x = 0;
if(x > 0)
    x = "PL";
x = 1+2;

 


 

타입 검사 조합의 필요성

  • 거의 모든 고급 프로그래밍 언어는 정적 및 동적 타입 검사를 모두 수행한다.
  • 언어는 정적 타입 검사를 사용하지만 경우에 따라 동적 검사가 필요하다.
    • 예) 배열 인덱스 바운드 검사
    • 배열의 크기가 동적으로 결정되는 경우 해당 인덱스 경계에 대한 확인도 동적이어야 한다.

 


 

스칼라 및 복합 타입

  • 스칼라 타입 : 서로 다른 값을 집계하지 않는다.
    • 부울, 문자, 정수, 실수.
    • 열거(Enumerations)
      • type days = { Mon,Tue,Wed,Thu,Fri,Sat,Sun }
    • 간격 : 1~10
  • 복합 타입 : 비스칼라 타입
    • 레코드(또는 구조), 배열(또는 벡터), 세트, 포인터, 함수, 재귀 타입 등
    • 이러한 타입에는 다른 작업이 있을 수 있다.

 


 

타입 동등성(Type Equivalence)

  • 이름 동등성(Name Equivalence) : 이름이 동일하면 두 타입이 동일하다.
  • 구조 동등성(Structural Equivalence) : 구조가 동일하면 두 타입이 동일하다.
  • 선언 동등성(Declaration Equivalence) : 함께 선언되면 두 타입이 동일하다.
  • 참조 투명성 : 프로그램의 의미를 변경하지 않고 어떤 상황에서든 두 개의 동등한 타입을 서로 대체할 수 있다.
  • 현대 언어에서는 예외를 제외하고 규칙 중 하나를 사용하는 경우가 많다.

 


 

이름 동등성

  • 타입 정의를 위해 의사 언어(pseudo)를 사용
type <type_name> = <expression>;

type Type1 = int;
type Type2 = int;
type Type3 = 1..100;
type Type4 = 1..100;
  • 이름 동등성은 매우 제한적인 규칙이다.
    • 위의 모든 타입이 다르다.
  • Java, C++는 대부분의 타입에 대해 이름 동등성을 사용한다.

 


 

구조 동등성

  • 두 타입이 동일한 구조를 갖는 경우 동일하다.
  • 더 느슨한 제약 조건
type Type1 = int;
type Type2 = int;
type Type3 = 1..100;
type Type4 = 1..100;
  • Type1/Type2는 동일하고 Type3/Type4는 동일하다.
  • Java 배열, C 배열, typedef.

 

  • 모호한 경우가 있다.
  • 다른 필드 이름
type Type1 = struct {
    int a;
    int b;
}
type Type2 = struct {
    int n;
    int m;
}
  • Type1/Type2가 동일한가?
    • 언어에 따라 다르지만 다른 필드 이름은 다른 것으로 간주된다.

 

  • 재귀 타입
type Type1 = struct {
    int a;
    Type2 b;
}
type Type2 = struct {
    int a;
    Type1 b;
}
  • Type1/Type2는 동일한가?
    • 타입 검사는 이러한 상호 재귀를 해결할 수 없으므로 다른 것으로 간주된다.

 


 

선언 동등성

  • 이름과 구조적 동등성의 중간에 있다.
  • 약한 이름 동등성 : 고려 타입은 간단한 이름 바꾸기 또는 함께 선언된 경우(예: Pascal)와 동일하다.
type Type1 = int;
type Type2 = Type1;
type Type3 = 1..100;
type Type4 = 1..100;
  • Type1/Type2는 동일하지만 Type3/Type4는 여전히 다르다.

 


 

타입 호환성(Type Compatibility)

  • 타입 T의 값이 타입 S의 값이 사용되는 모든 컨텍스트에서 사용될 수 있는 경우 타입 T는 타입 S와 호환된다.
  • 보다 구체적으로, 타입 T와 S는 다음과 같은 경우에 호환된다.

 

  1. T형과 S형은 동일하다.
    • 참조 투명성

  2. T의 값은 S의 값의 하위 집합이다.
    • 간격 1..10, 1..100.

  3. S의 모든 연산은 T에 적용될 수 있다.
    • type S = struct { int a; }
    • type T = struct { int a; char b; }
    • S의 가능한 작업은 필드 a에 액세스하는 것뿐이다.
    • T⊄S, 그러나 T의 a만을 취해 S의 연산을 적용할 수 있다.

  4. T의 값은 정식 방식으로 S의 값에 대응된다.
    • T : 정수
    • S : 부동 소수점
    • T는 S의 하위 집합이 아니지만 float에 int를 사용할 수 있습니다(예: 2.0의 경우 2).

  5. T의 값은 변환을 통해 S의 값으로 변환될 수 있다.
    • float는 반올림(예: C에서 반올림)을 통해 int로 변환될 수 있다.

 


 

타입형 변환(Type Conversion)

  • 암시적 변환(강제) (Implicit Conversion(coercion))
    • 강제 변환이라고도 한다. 타입 변환은 컴파일러에 의해 수행된다.
    • T와 S 유형이 호환되면 프로그래머가 지정하지 않은 경우에도 자동으로 변환이 수행된다.
  • 명시적 변환(캐스트) (Explicit Conversion (cast)) 
    • 프로그래머는 타입 변환을 명시적으로 나타낸다.
    • S s = (S) t;

 


 

업 캐스팅 vs. 다운 캐스팅

  • 상속된 클래스 간의 캐스팅이 가능할 수 있다.
  • 업 캐스팅(Upcasting) : 자식을 부모로 변환
    • 암시적 변환이 가능하다.
    • 예) Parent p = new Child();
  • 다운 캐스팅(Downcasting) : 부모를 자식으로 변환
    • 명시적인 변환이 필요히다.
    • 예) Child c = (Child) p;

 


 

타입 확인 및 추론

  • 타입 검사(Type Checking) : E라는 표현식과 T 타입이 주어졌을 때 E가 T 타입인지 확인
    • int f(int a) { return a+1; }
    • a+1은 정수여야 한다.
  • 타입 추론(Type Inference) : E라는 표현식만 주어지면 E의 타입을 파생시킨다.
    • def f(a): return a+1
    • 1은 int이고 +는 두 개의 정수를 취하므로 a는 int여야 하며 함수 f()는 int->int입니다.

 


 

타입 안전성(Type Safety)

  • 이러한 모든 타입 검사 및 추론은 언어의 타입 안전성을 확보하기 위한 것이다.
  • 타입 시스템(또는 언어)은 어떤 프로그램도 언어에 정의된 타입 구별을 위반할 수 없을 때 타입이 안전하다.
  • 이론적으로 타입 안전성은 생각보다 더 제한적이다.
    • 안전하지 않은 언어 : C, C++와 같이 메모리에 직접 액세스하기 위한 포인터가 있는 언어(메모리 안전 문제)
    • 지역적으로 안전한 언어 : 일부 언어(예: Pascal)에는 안전하지 않은 부분이 포함되어 있다.
    • 안전한 언어 : 이론적으로 이러한 언어는 숨겨진 유형 오류(예: Scheme, ML, Java)를 생성하지 않는다.

댓글