타입

최근 편집: 2019년 2월 18일 (월) 23:28
(동적 타이핑에서 넘어옴)

타입은 특수한 데이터 유형을 가리키는 컴퓨터 공학 용어로, 정수, 부동소수점 실수, 문자, 문자열 등이 있다. 즉, 서로 다른 데이터 종류를 타입이라고 칭한다. 숫자 -213과 문자열 "abc"는 서로 다른 타입이다.

더 엄밀한 정의

그러나 이러한 추상적인 정의만으로는 불충분하므로, 프로그래밍 언어를 설계할 때 고려해야 할 부분을 위주로 어떤 타입을 더 엄밀하게 정의할 수 있다.

타입이란 데이터 도메인과 연산자 집합으로 이루어진 순서쌍이다. 예를 들어, 4바이트 정수는 단순하게 다음과 같이 정의할 수 있다.

Integer = <D, O>

= <{-231, ‥, -2, -1, 0, 1, 2, ‥, 231-1}, {+, -, ×, ÷}>

연산자는 데이터 도메인에 속하는 임의의 원소 하나 이상을 입력받아, 결과가 되는 어떤 데이터를 반환하는 함수로 볼 수 있다. 예를 들어, 정수에 대한 이항 연산자 +는 다음과 같이 정의할 수 있다.

+: (Integer, Integer) → Integer = <…어떤 조건의 연쇄…>

어떤 연산자에 대하여, 연산자의 공역이 정의역에 포함될 경우 이 연산자는 해당 타입에 대하여 닫혀 있다(closure). 반면 연산자의 연산 결과가 정의역에 포함되지 않을 수도 있을 경우, 이 연산자는 열려 있다. 예를 들어, 나눗셈 연산자는 수학적으로 볼 때 정수 집합에 대하여 열려 있다.

프로그래밍 언어의 경우, 연산자의 특성을 정확하게 이해하지 않을 경우, 버그의 원인이 되기 쉽다. 예컨대, 4바이트 정수형 데이터에게 결과값이 -231 미만이거나 231 이상일 수 있는 연산을 명령했을 경우, 정수형을 4바이트로 규정하는 타입 체계를 채택하는 언어로 프로그램을 작성했을 경우, 실행 시간에 오버플로가 발생할 수 있다.

추상적 데이터 타입(ADT, 자료 구조)

위에서 다룬 바와 같이, 프로그래밍 언어에서 타입은 정의역과 연산자 집합으로 구성되고, 일반적인 상황에서는 입력 자료의 타입과 값에 의해 출력 자료가 논리적으로 결정된다. 이처럼 개발 과정의 유용성을 위해 논리적으로 정의되어 프로그래머들 사이에서 공유되는 타입 명세를 가리켜 추상적 데이터 타입이라고 부르며, 보통 기본적 데이터 타입(정수, 실수, 문자, 논리값)과 복잡한 데이터 타입(문자열, 정적 배열 등), 그리고 더 복잡한 데이터 타입(스택, 큐, 힙, 트리, 동적 배열 등)으로 나누어 생각할 수 있다.

비트, 워드부터 스택이나 트리 같은 타입에 이르기까지, 모든 데이터 타입은 실세계의 개체가 아니라 논리를 사용하여 정의된다는 점에서 추상적 데이터 타입이다. 그러나 보통 개발자들은 흔히 연산자가 충분히 복잡하여 응용 프로그램 단위에서 구현이 필요하고 동적으로 데이터 크기가 확장·축소될 수 있는 타입(즉, 스택, 큐, 힙, ……)만을 ADT라고 부르고는 한다. 그러나 원론적으로 볼 때, 모든 데이터는 ADT에 의해 규정된다.

설계 쟁점: 데이터의 타입 묶음(binding) 시점

상기했듯, 타입은 데이터에 연산자가 연관된 것이다. 그렇다면, 컴퓨터 상에 저장되는 모든 데이터가 본질적으로 비트열임을 감안할 때, 이 데이터를 비트열이 아니라 문자열이나 스택, 힙 등으로 간주하게끔 연산자를 연관시키는 시점이 있어야 한다. 이처럼, 연산과 데이터를 연관시키는 것을 묶음(binding) 또는 바인딩이라고 한다. 어떤 설정된 데이터에 타입을 바인딩하는 것을 타입 결정(typing) 또는 타이핑이라고 한다.

프로그램에 속한 모든 데이터의 타입 결정 시점이 프로그램 실행 시간 전/후냐에 따라 정적 타이핑과 동적 타이핑을 나눌 수 있다. 정적 타이핑을 기본으로 채택하는 대표적인 언어는 C와 C 계열 언어이다. 다음 예시를 보자.

int x = 10;

위 선언은 컴파일되면서 다음과 같은 작업을 시행한다. ①어떤 작업 공간에 x라는 식별자를 할당한다. ②x에 int 타입을 바인딩한다. ③x의 주소에 int 타입 리터럴 '10'에 해당하는 비트값을 할당한다.

이와 같은 작업은 언어 사용자가 코드를 작성한 후 컴파일 시점에 완료되며, 프로그램 실행 시간에 x의 타입은 바뀌지 않는다.(일반적으로.)

반면 파이썬은 동적 타이핑을 기본으로 채택하는 언어이다. 다음 예시를 보자.

# f()는 x라는 멤버를 갖는 어떤 객체에 소속된다고 가정
def f(self, cond):
    if condition > 0:
        self.x = 10
    else:
        self.x = "abcdef"

위 코드에서, self 객체의 멤버 x는 f에 주어진 인자가 0보다 큰가에 따라 숫자가 될 수도 있고, 문자열이 될 수도 있다. 그렇다면, 주어진 인자가 0보다 큰지 확인할 수 있는 시점은 언제인가? 당연히 실행시간에 호출자가 f()를 호출한 이후이다. 즉, 파이썬은 데이터의 타입을 실행 이후에 결정하는 것이 기본이다.