사용 개념: 타입 추론 규칙(식의 타입), 타입 검사 절차(문장의 WF 규칙과 도는 순서).
네 문제 모두 같은 스코프 \(S\)를 쓴다. \(S\)에는 다음이 선언되어 있다1.
int[] arr; int i; int n; bool flag; double d;
double f(int, bool);
규칙은 위 두 개념 문서에 적은 것만 쓴다. 양쪽 타입이 다른 덧셈과 비교는 맞는 규칙이 없어 오류다(자동 변환 없음).
문제 1 · 식의 타입 구하기
f(arr[i + 1], flag)의 타입을 구하고, 규칙을 쓰는 순서를 쓰라.
풀이 전략. 식을 트리로 그리고 잎부터 올라간다. 노드 하나에 규칙 하나다. 맨 바깥이 함수 호출이니, 인자 두 개의 타입을 먼저 구해 f의 선언 (int, bool) → double과 맞춰 본다.
풀이
- 잎.
arr은 변수 규칙으로int[].i는 변수 규칙으로 int.1은 상수 규칙으로 int. - 덧셈.
i + 1은 양쪽이 int라 int. - 배열.
arr[i + 1]은 \(e_1\)이int[], \(e_2\)가 int라 int. - 잎.
flag는 변수 규칙으로 bool. - 함수 호출. 인자의 타입이 차례로 (int, bool)이고 f의 선언과 개수와 타입이 모두 맞는다. 결과는 double.
타입은 double이고 규칙은 7번 쓴다(arr, i, 1, +, [], flag, f).
변형. f(arr[i + 1], flag) + d의 타입은? f(arr, flag)는?
답
앞의 것은 양쪽이 double이라 덧셈 규칙으로 double. 뒤의 것은 첫 인자가 int[]인데 f는 int를 바라므로 함수 호출 규칙의 가정이 깨져 타입 오류다.
문제 2 · 타입 오류 찾기
다음 다섯 식 가운데 타입 오류인 것을 모두 고르고, 어느 규칙의 어느 가정이 깨지는지 쓰라.
(가) i + 1 == n (나) d + i (다) flag && (i <= d) (라) arr[flag] (마) f(i, flag, n)
풀이 전략. 식마다 맨 안쪽 부분식부터 타입을 붙여 올라가다가, 처음으로 규칙을 못 쓰는 노드에서 멈춘다. 오류는 그 노드의 것이다.
풀이
- (가) 맞다.
i + 1은 int,n은 int. 비교 규칙으로 bool. - (나) 오류. 덧셈 규칙은 양쪽이 같은 \(T\)여야 하는데 double과 int다.
- (다) 오류.
i <= d에서 비교 규칙의 \(T\)가 int와 double로 갈린다.&&까지 가기 전에 안쪽에서 깨진다. - (라) 오류. 배열 규칙은 \(e_2\)가 int여야 하는데
flag는 bool이다. - (마) 오류. f는 인자 둘을 받는데 셋을 넘겼다. 함수 호출 규칙의 “\(1 \le i \le n\)인 모든 \(e_i\)가 \(T_i\)“에서 개수 \(n\)이 맞지 않는다.
변형. (다)를 한 글자만 고쳐 맞는 식으로 만들라.
답
flag && (i <= n). i <= n이 bool이 되고, &&의 양쪽이 bool이라 전체가 bool이다.
문제 3 · 문장 검사의 순서
다음 문장을 검사할 때 트리의 노드에 타입이나 WF가 붙는 순서를 쓰고, 모두 몇 번 붙는지 세라.
while (i <= n) {
d = f(i, flag);
i = i + 1;
}
풀이 전략. 문장마다 “부분식 → 자식 문장 → 전체” 순서다. while은 조건이 부분식이고 블록 안의 두 대입문이 자식 문장이다. 대입문 하나 안에서도 왼쪽 변수, 오른쪽 식, 그다음 대입 전체 순서다.
풀이
- while의 부분식.
i(int) →n(int) →<=(bool) → 조건(bool). - 첫 자식 문장.
d(double) →i(int) →flag(bool) →f(…)(double) →=(WF: 왼쪽이 변수이고 양쪽이 double). - 둘째 자식 문장.
i(int) →i(int) →1(int) →+(int) →=(WF). - 블록. 두 문장이 모두 WF라 블록이 WF.
- 전체. 조건이 bool이고 안쪽이 WF라 while이 WF.
모두 16번 붙는다. WF는 4번(= 둘, 블록, while)이다.
변형. 첫 문장을 i = f(i, flag);로 바꾸면 어디에서 무엇이 깨지는가?
답
그 대입문에서 깨진다. 왼쪽 i는 int, 오른쪽 f(…)는 double이라 대입 규칙의 “양쪽이 같은 타입 \(T\)“가 채워지지 않는다. 자식 문장이 WF가 아니므로 블록도 while도 WF가 아니다.
문제 4 · return과 if-else
다음 함수가 의미에 맞게 짜였는지 판정하라. 쓰는 스코프를 모두 밝혀라.
int abs(int x) {
if (x >= 0) { return x; }
else { return 0 - x; }
}
풀이 전략. return 규칙은 “지금 스코프가 어떤 타입을 돌려주는 함수 안인가”를 가정으로 쓴다. 블록이 겹쳐 있어도 그 함수의 반환 타입은 안쪽 스코프까지 따라간다. if-else 규칙은 조건과 두 블록을 따로 본다.
풀이
스코프는 넷이다. 전역, 함수 안쪽(x : int가 있다), if 블록 안쪽, else 블록 안쪽. 뒤의 셋은 모두 “int를 돌려주는 함수 안”이다.
- 조건.
x(int),0(int),x >= 0은 bool. - if 블록.
return x;에서 x는 int이고 함수가 int를 돌려주므로 WF. 블록도 WF. - else 블록.
0(int),x(int),0 - x는 int.return 0 - x;가 WF. 블록도 WF. - if-else 전체. 조건이 bool이고 두 블록이 각자 스코프에서 WF라 WF.
- 함수. 몸통이 WF다. 의미에 맞게 짜였다.
변형. (a) 반환 타입을 bool로 바꾸면? (b) else 쪽만 return x >= 0;으로 바꾸면? (c) 조건을 if (x)로 바꾸면?
답
(a) 두 return 모두 int를 돌려주는데 함수는 bool을 돌려주므로 return 규칙이 둘 다 깨진다. (b) else 쪽 return이 bool을 돌려줘 그 한 문장이 깨지고, 그래서 else 블록, if-else, 함수가 모두 WF가 아니다. (c) 조건이 int라 if-else 규칙의 첫 가정 “조건이 bool”이 깨진다. 두 블록은 멀쩡해도 전체는 WF가 아니다.
-
-의 규칙은+와 같은 꼴로, if-else의 WF 규칙은 while 규칙과 같은 꼴로 두었다(개념 문서의 보충 참고). 답은 문제 코드로 확인했다. ↩