티스토리 뷰

타입 챌린지 Greater Than에 도전합니다.

 

type-challenges/questions/04425-medium-greater-than/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 


 

서론

타입 GreaterThan<T, U>를 구현하는 문제입니다.

문제는 간단합니다. number인 T와 U를 입력받고, `T가 U보다 큰 경우 true를 반환`하면 됩니다.

`T와 U가 같거나 작은 경우에는 false를 반환`하고, 음수의 경우는 생각하지 않습니다.

 

타입시스템에서는 연산자를 사용할 수 없습니다.

때문에 직접적인 정수 연산을 이용하는 것을 불가능하고, 오직 포함 관계와 재귀를 통해서 문제를 해결해야 합니다.

 

여기서는 배열 타입을 이용해 볼 수 있습니다.

 

 

배열과 인덱스를 이용한 비교

타입스크립트는 배열 타입을 지원합니다. 인덱스를 이용해서 요소를 가져올 수도 있죠.

숫자외에도 `${infer n extends number}`형식의 문자열을 인덱스로 사용하는 것도 가능합니다.

type test1 = [0, 0][0];  // type test1 = 0
type test2 = [1, 2, 3][1]; // type test2 = 2
type test3 = [any, any, any]['2']; // type test3 = 3
type test4 = [1 ,true, 'a', undefined][100]; // 해당 배열의 인덱스 범위가 아닙니다. + type test4 = undefined
type test5 = [undefined, null, undefined, any][-1]; // 인덱스는 음수가 들어갈수 없습니다. +  type test5 = undefined

 

 

인덱스 대신 'length'를 지정하면 해당 배열의 길이를 추론해 보여줍니다. (들어있는 요소의 개수를 number로 반환합니다.)

type test1 = [0, 0]['length'];  // type test1 = 2
type test2 = [1, 2, 3]['length']; // type test2 = 3
type test3 = [any, any, any]['length']; // type test3 = 3
type test4 = [1 ,true, 'a', undefined]['length']; // type test4 = 4
type test5 = [undefined, null, undefined, any]['length']; // type test5 = 4

 

 

 

이런 특징을 이용해 정수 비교를 구현할 수 있습니다.

 

배열 `[0, 0, 0]`과 `[0, 0]`이 있다고 해보겠습니다. 각각의 length는 3, 2으로 추론되겠죠.

type arr1 = [1, 2, 3]
type arr2 = [4, 5]

type length1 = arr1['length'] // type length1 = 3
type length2 = arr2['length'] // type length2 = 2

 

 

서로의 length를 인덱스로 지정하면 어떻게 될까요?

type test1 = arr1[length2] // type test1 = 3
type test2 = arr2[length1] // 해당 배열의 인덱스 범위가 아닙니다. + type test2 = undefined

 

test1는 arr1[2]의 결과가 추론될 겁니다. 그래서 해당 인덱스의 요소 타입인 3이 추론되었습니다.

test2는 arr2[3]의 결과가 추론될 겁니다. 하지만 arr2의 인덱스 범위를 초과했기 때문에 undefined으로 추론됩니다.

 

 

두 배열의 길이가 같다면 어떤 결과가 나올까요?

type arr1 = [1, 2]
type arr2 = [3, 4]

type test1 = arr1[arr2['length']] // 해당 배열의 인덱스 범위가 아닙니다. type test1 = undefined
type test2 = arr2[arr1['length']] // 해당 배열의 인덱스 범위가 아닙니다. type test2 = undefined

 

두 배열 다 undefined으로 추론됩니다.

 

 

한쪽이 빈배열이면 어떻게 될까요?

type arr1 = [1, 2]
type arr2 = []

type test1 = arr1[arr2['length']] // type test1 = 1
type test2 = arr2[arr1['length']] // 해당 배열의 인덱스 범위가 아닙니다. type test2 = undefined

 

빈배열의 length는 0으로 추론되기 때문에 위에서 봐왔던 것과 동일하게 작동합니다.

 

 

즉 배열 타입 A와 B가 있을 때, 

A['length'] > B['length']이라면 A[B['length]]는 A의 요소의 값으로 추론된다는거죠.

* A, B에는 undefined이 들어있지 않아야 합니다. A[B['length]]로 추론되는 값이 undefined이라면 이 로직를 이용할 수 없습니다.

 

 

 

구현하기

위 내용 이용해 GreaterThan을 구현해 볼까요?

type GreaterThan<T extends number, U extends number> = any

 

NewArray

먼저 T와 U를 배열로 바꾸어줄 타입을 만들어 보겠습니다.

type NewArray<T extends number, R extends any[] = []> = R['length'] extends T ? R : NewArray<T, [...R, 0]>

 

 

NewArray<T, R>은 길이가 T인 배열을 반환하는 타입입니다. 

다음과 같이 T만큼 0으로 채워진 배열타입을 반환합니다. 저는 원하는 길이의 배열타입을 만들 때 NewArray<T, R>를 사용합니다.

type arr3 = NewArray<5> // type arr3 = [0, 0, 0, 0, 0]
type arr4 = NewArray<1> // type arr3 = [0]
type arr5 = NewArray<0> // type arr3 = []

 

 

R은 optional 타입파라미터 입니다. 값을 명시하지 않으면 기본값인 []으로 작동하게 되죠.

R을 명시적으로 주었는데 길이가 T보다 클 경우, 무한 루프가 발생해 에러가 난다는 점을 유의해 사용해야 합니다. 💡

type arr3 = NewArray<5, [1, 2]>	// type arr3 = [1, 2, 0, 0, 0]
type arr4 = NewArray<1, [1, 2]>	// inf err
type arr5 = NewArray<0, []> 	// type arr3 = []

 

 

이런 특정 타입을 구현하는 유틸성 타입은 내부 구현으로 빼면 되기 때문에,

예외사항을 알고 통제할 수 있다면 크게 문제가 되지 않습니다.

 

 

 

GreaterThan

우리가 검사하고 싶은건 'T가 U보다 큰가?'입니다. 따라서

1. 숫자 T만큼의 배열을 만들고 (NewArray 이용)

2. 만들어진 배열에 U를 인덱스로 넣어 어떤 타입이 나오는지 검사합니다.

3. undefined이라면 T의 인덱스 범위를 벗어났다는 뜻, 즉 U가 더 크다는 의미임으로 false를 반환합니다.

4. undefined이 아니라면 true를 반환합니다.

 

 

이를 코드로 구현하면 다음과 같습니다.

type GreaterThan<T extends number, U extends number> = type GreaterThan<T extends number, U extends number> = [...NewArray<T>][U] extends undefined ? false : true

 

 

`NewArray<T> [U]`가 아니라 `[...NewArray<T>][U]`로 작성한 이유는 위에서 언급한 무한루프가 발생할 경우 때문입니다.

우리는 내부적으로 NewArray에 T값만 주었으니 무한루프가 일어나지 않을 것을 알지만 타입스크립트 입장에서는 이를 모릅니다.

반환되는 타입을 보면 여전히 무한루프가 일어날 가능성이 있으니까요.

 

무한루프가 발생하게 되면 NewArray는 any 타입이 반환됩니다. 그렇다면 배열타입이 아니므로 인덱스를 가질수 없는거죠.

따라서 NewArray가 배열임을 타입스크립트에게 명시적으로 알려주기 위해 전개연산자를 이용한 겁니다. 💡

 

 

 

 

문제점

타입스크립트는 기본적으로 재귀호출이 일어나는 depth를 제한하고 있습니다.

타입추론을 위해 너무 많은 메모리를 사용하는 것을 막기 위해서죠.

결국 유틸리티 타입은 비지니스 로직을 위한 기능이 아니라, 개발자의 편리를 위한 기능이니까요.

 

depth의 기본설정은 999입니다.  따라서 NewArray는 T가 1000 이상이 되면 작동하지 않습니다.

 

GreaterThan<1234567891011, 1234567891010> // inf err

 

 

 큰 수에 대한 비교는 다른 방법이 필요할 것 같습니다.🤔

 

 


 

 

공부한 내용을 복습/기록하기 위해 작성한 글이므로 내용에 오류가 있을 수 있습니다.

댓글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Total
Today
Yesterday