- Published on
2023.12.25
타입으로 견고하게 다형성으로 유연하게 - 01. 타입 검사 훑어보기
1.2. 정적 타입 언어
'정적'이라는 단어는 '프로그램을 실행하기 전에'를 뜻한다.
정적 타입 언어
란 프로그램을 실행하기 전에 타입이 올바르게 사용되었는지 확인하는 언어다. 반대로, 동적 타입 언어
란 타입 검사기를 제공하지 않아 프로그램을 실행하기 전에 타입 검사를 해 볼 수 없다. '동적'은 '프로그램을 실행하는 중에'를 뜻한다. 타입이 잘못되었는지는 실행 중에야 파악할 수 있다.
1.3. 타입 검사의 원리
정적 타입 언어를 잘 쓰려면 타입 검사기의 오류 메시지를 이해할 수 있어야 한다. 그러기 위해서 타입 검사기가 어떻게 작동하는지 알아야 한다. 타입 검사기는 작은 파트에서 큰 파트로 가면서 프로그램을 검사한다.
printInt(5 + 7)
이 코드는 두 개의 파트, printInt
와 5 + 7
로 구성되어 있다. 이 두 파트가 함수 호출이라는 형태로 모여 하나로 만들어졌다. 각각의 파트를 보면 printInt
는 함수의 이름으로, 더 이상 쪼갤 수 없는 단위다. 5 + 7
은 각각 5
와 7
로 나눌 수 있다. 각각 더 이상 쪼갤 수 없는 단위로, 덧셈이라는 형태로 모여 하나를 구성한다.
타입 검사기가 위 코드를 검사하는 과정은 다음과 같은 흐름을 따라간다.
기본 부품 검사 먼저 기본 부품을 검사하는데, 가장 작은 부품인
5
와7
을 검사해 타입을 알아낸다. 각각의 타입이 정수인 것을 알아낸다.복합 부품 검사 복합 부품을 구성하는 작은 부품들의 타입이 올바른지 확인한다.
5 + 7
은5
와7
로 이루어진 복합 부품이며, 덧셈으로 결합하려면 각각의 부품이 정수 타입이어야 한다. 1에서 두 부품이 정수 타입인 것을 확인했으므로 문제가 없다. 덧셈의 결과는 정수이므로5 + 7
의 결과도 정수 타입인 것을 알아낸다.복합 부품 검사 반복 2를 반복하며 복합 부품을 검사한다.
printInt(5 + 7)
은printInt
와(5 + 7)
로 이루어진 복합 부품이므로printInt
의 요구 타입이 정수인 것과 인자인5 + 7
이 정수 타입으로 일치하는지 확인한다.검사 완료 검사 과정 중에 타입이 모두 올바른 것을 확인했으므로 검사를 통과한다.
만약 (5 + "7")
에 대해 타입 검사를 하면 복합 부품 검사 과정에서 덧셈이 요구하는 부품의 타입이 정수인 점과 "7"
이 문자열 타입으로 일치하지 않아 문제가 발견되고, 타입 검사가 통과하지 못하고 에러를 출력한다.
타입 검사기는 부품을 구체적으로 어떻게 검사할까.
리터럴
리터럴을 검사하는 것은 가장 쉬운 경우다. 정수 리터럴은 더 이상 쪼개지지 않는 기본 부품이니 타입만 찾으면 된다. 정수 리터를의 계산 결과도 반드시 정수다. 그러므로 정수 리터럴이라는 부품의 타입은 정수 타입이다. 다른 종류의 리터럴도 마찬가지다.
덧셈
덧셈을 하려면 주어진 값이 Int여야 하므로, 주어진 값들의 타입이 Int인지 확인한다. 하나라도 Int가 아니라면 타입 오류를 일으킨다. 그런데 자바스크립트 같은 경우는 문자열의 덧셈도 허용이 된다. 주어진 값들이 타입이 String, Number로 서로 달라도 오류를 일으키지 않는다. 이를 통해, '덧셈은 Int 타입을 요구한다'가 아니라 '덧셈은 특정 타입을 요구하고, 부품이 그 요구를 만족하는지 확인한다.'라는 점이 타입 검사기 동작의 핵심인 것을 알 수 있다. 다른 연산도 다르지 않다.
삼항 연산자
a ? b : c
삼항 연산자를 검사할 때는 a, b, c의 타입이 올바른지 확인한다. 먼저 a는 Boolean이어야 한다. b와 c의 타입은 미리 알 수 없다. a의 결과가 true
일지 false
일지 알 수 없어 b와 c 중 무엇이 계산될 지도 알 수 없다. 만약 b와 c의 타입이 서로 다르면 결과의 타입이 달라지게 되므로 문제가 된다. 그래서 첫 단계에서 b와 c의 타입은 같아야 한다고 요구해 두 번째 단계에서 b와 c의 타입이 공통된 타입이라고 보고 검사를 진행한다.
여기서, 프로그램이 타입 오류를 실제로 일으키는지와 타입 검사를 통과하느냐는 다른 얘기다. true ? 1 : false
를 실행하면 오류가 없다. true
가 boolean이고, 항상 1을 리턴하기 때문이다. 그런데 타입 검사에서는 1
과 false
의 타입이 서로 다르기 때문에 검사에서 오류를 발생시킨다. 왜냐면 타입 검사에서는 a의 타입이 boolean인지만 확인하고, 계산한 실제 결과를 반영하지 않기 때문이다. a의 계산이 많은 시간을 요구한다면 타입 검사에도 많은 시간이 들어가기 때문에, 타입 검사는 a의 결과를 확인하지 않고, boolean인지만 확인하고 넘어간다. 그리고 b와 c의 타입이 같다는 전제하에 나머지 타입 검사를 진행한다.
변수
리터럴, 덧셈, 삼항 연산자는 따로 타입을 정의하지 않아도 되지만, 변수나 함수는 개발자가 직접 정의해야 한다. 변수의 값을 읽는 부품은 더 이상 쪼갤 수 없는 기본 부품이니 부품의 타입만 알아내면 되지만, 변수의 이름에는 정보가 없다. 그래서 정적 타입 언어는 변수의 타입을 직접 명시할 것을 요구한다. 이후 변수에 새로운 값을 쓰더라도 기존에 변수에 지정한 타입과 새로 쓰는 값의 타입이 같아야 한다.
함수
함수도 정의할 때 타입을 명시해 주어야 한다. 함수의 타입은 매개변수 타입
과 결과 타입
으로 구성된다.
Boolean isPositive(Int num) { return num > 0; }
매개변수 num 앞에 Int는 매개변수 타입으로, 인자의 타입을 나타낸다. 함수 이름 앞의 Boolean은 결과 타입으로, 함수가 리턴하는 값의 타입을 나타낸다. 인자는 인자의 타입을, 결과는 결과의 타입과 비교하여 일치하면 타입 검사를 통과한다.
한 가지 특별한 경우는 함수가 어떤 값도 리턴하지 않는 경우로, 이때는 결과 타입을 Void
로 쓴다. Void
는 함수의 결과 타입에만 사용할 수 있는 특별한 타입으로, return에 아무 값도 주어지지 않았는지 확인한다.
1.4. 타입 검사 결과의 활용
코드 편집기
코드 편집기는 자동 완성과 이름 바꾸기 기능을 제공하는데, 정적 타입 언어에서 이 기능들을 최대로 활용할 수 있다. 명확한 타입을 통해 자동 완성 기능이 더욱 정확하게 작동할 수 있는데, 여기서 타입 검사기가 코드 편집기를 도와 정확하게 추측할 수 있도록 도와준다.
이름 바꾸기는 이미 정의한 변수, 함수, 메서드 등의 이름을 바꿔주는데 한 곳에서 이름을 바꾸면 같은 이름이 사용된 다른 곳에서도 다 같이 바뀐다. 문제는 서로 다른 대상이 같은 이름을 가진 경우인데, 이 때 타입 검사기가 이름이 가르키는 대상이 무엇인지 알려주어 잘못될 일이 없어진다.
프로그램 성능
정적 타입 언어는 프로그램 실행 시간이 짧다. 이는 타입 검사 덕으로, 동적 타입 언어는 실행 중에 인자 등의 타입을 모두 확인해야하지만, 정적 타입 언어는 그럴 일이 없어 효율적으로 실행할 수 있다.
...이어서...