TypeScript: 유형 기반 이진 가산기

17909 단어 webdevfunnytypescript
기사에 설명된 모든 내용은 TypeScript 4.2.3 버전과 관련이 있습니다.

안녕하세요!
TypeScript는 이제 Variadic Tuple Types 을 지원합니다. 배열과 같은 유형을 다른 유형으로 변환하는 유형을 생성하기 위해 확산 연산자 및 일반 매개변수를 사용할 수 있음을 의미합니다. 예를 들어 배열에 요소를 추가하는 유형을 만들어 보겠습니다.

type Append<T extends unknown[], R> = [R, ...T];

let a1: Append<[1, 2, 3], 0>;
//   ^ = let a1: [0, 1, 2, 3]
let a2: Append<[], "some">;
//   ^ = let a2: ["some"]
let a3: Append<[1, "q", false], {a: number}>;
//   ^ = let a3: [{a: number}, 1, "q", false]


playground

좋습니다. 하지만 배열의 첫 번째 요소를 제거하는 유형을 만들고 싶다면 어떻게 해야 할까요? 이 유형은 빈 배열[]이 확장unknown[]하고 원래 배열에 요소가 있는지 확인해야 하기 때문에 그렇게 간단하지 않습니다. conditional typetype inference을 사용하여 이를 수행할 수 있습니다.

type Slice<T extends unknown[]> = T extends [infer _, ...infer Tail] 
  ? Tail 
  : [];

let b1: Slice<[1, 2, 3]>;
//   ^ = let b1: [2, 3]

let b2: Slice<[1]>;
//   ^ = let b2: [];

let b3: Slice<[]>;
//   ^ = let b3: [];


playground

작업을 복잡하게 합시다. 배열을 뒤집을 유형을 만드는 방법은 무엇입니까? 일반적인 상황에서는 루프 또는 재귀를 사용하여 배열을 반복합니다. Typescript 유형에서는 recursive type alias 을 사용할 수 있습니다.

type Reverse<T extends unknown[]> =
T extends [infer Item, ...infer Tail] 
  ? [...Reverse<Tail>, Item] 
  : [];

let c1: Reverse<[1, 2, 3]>;
//   ^ = let c1: [3, 2, 1]

let c2: Reverse<[1]>;
//   ^ = let c2: [1]

let c3: Reverse<[]>;
//   ^ = let c3: []


playground

이 접근법에는 불쾌한 순간이 하나 있습니다. typescript는 추론 유형을 정의하기 위해 제네릭의 제약 조건을 사용하지 않습니다. type Some<T extends number> = T가 있는 경우 정의된 유형을 T 매개변수로 유추할 수 없습니다.

type Some<A extends number> = A;

type Some2<A extends number[]> = A extends Array<infer B> 
  ? Some<B> 
//       ^ Error: Type 'B' does not satisfy the constraint 'number'
  : number;


그러나 Some 유형을 type Some<T> = T extends number ? T : number 로 변경하면 유추된 유형을 사용할 수 있습니다. Some 유형의 이 두 버전은 동일하지 않지만 때때로 이 접근 방식을 사용할 수 있습니다.

유형 기반 이진 가산기



자, 이제 우리는 이진 가산기를 만드는 모든 것을 알고 있습니다. 이진 가산기가 무엇인지 모르는 경우 learn it yourself 할 수 있습니다. 먼저 몇 가지 논리 연산을 생성합니다.

type Xor<A, B> = A extends B ? 0 : 1;
/*
  A  B  Xor<A, B>
  0  0  0
  1  0  1
  0  1  1
  1  1  0
*/

type And<A, B> = A extends 1 ? B extends 1 ? 1 : 0 : 0;
/*
  A  B  And<A, B>
  0  0  0
  1  0  0
  0  1  0
  1  1  1
*/

type Or<A, B> = A extends 1 ? 1 : B extends 1 ? 1 : 0;
/*
  A  B  Or<A, B>
  0  0  0
  1  0  1
  0  1  0
  1  1  1
*/


둘째, 가산기 회로를 고려하십시오.

보시다시피 우리는 2개의 숫자의 2비트를 사용하고 캐리 인 및 리턴 결과 비트와 캐리 아웃을 사용해야 합니다.

type Sum<A, B, CIn> = Xor<Xor<A, B>, CIn>;
type COut<A, B, CIn> = Or<And<A, B>, And<Xor<A, B>, CIn>>;


마지막으로 가산기 유형을 만듭니다.

type Add<A, B, CIn = 0> = 
  A extends [...infer AN, infer AC] 
    ? B extends [...infer BN, infer BC]
      ? [...Add<AN, BN, COut<AC, BC, CIn>>, Sum<AC, BC, CIn>]
      : []
    : [];

let d1: Add<[0], [1]>
//   ^ = let d1: [1]

let d2: Add<[0, 1], [0, 1]>
//   ^ = let d2: [1, 0]

let d3: Add<[0, 1, 1, 1], [0, 0, 0, 1]>
//   ^ = let d3: [1, 0, 0, 0]

let d4: Add<[1, 1, 1, 1], [0, 0, 0, 1]>
//   ^ = let d4: [0, 0, 0, 0]


playground

다른 이진 연산은 이러한 원칙을 사용하여 구현할 수 있습니다. 실제 프로젝트에서 이러한 유형이 필요합니까? 나는 그렇게 생각하지 않는다. 하지만 이러한 원칙이 정말 유용한 유형을 만드는 데 도움이 될 것이라고 생각합니다. 저를 구독하시면 흥미로운 유형의 TypeScript에 대해 자세히 알려 드리겠습니다.

좋은 웹페이지 즐겨찾기