Skip to content

Commit

Permalink
feat: 함수형 디자인 Typescript에서의 TDD 포스트 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
alstn2468 committed Dec 5, 2021
1 parent 775da24 commit c31567c
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 26 deletions.
26 changes: 0 additions & 26 deletions .travis.yml

This file was deleted.

150 changes: 150 additions & 0 deletions content/blog/TypeScript/2021-12-05-fp-ts-funtional-design-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
title: '함수형 디자인: Typescript에서의 TDD (declare의 남용)'
date: 2021-12-05 00:01:04
category: 'TypeScript'
draft: false
---

본 포스트는 fp-ts 공식 문서의 [Learning Resources](https://gcanti.github.io/fp-ts/learning-resources/)에 있는 Functional design series에서 소개하는 문서들을 번역하며 학습한 문서입니다. 원본 문서는 [링크](https://dev.to/gcanti/functional-design-tdd-in-typescript-aka-abusing-declare-59il)에서 확인할 수 있으며 작성한 코드들은 [여기](https://github.com/alstn2468/getting-started-fp-ts/tree/main/src/functional_design_series/5_tdd_in_typescript)에서 확인할 수 있습니다.

## 함수형 디자인: Typescript에서의 TDD (`declare`의 남용)

**타입** 주도 개발(TDD)은 문제를 더 작은 문제들로 나누기 위해 사용되는 기술로, 타입 검사기가 구체적인 구현을 제안하거나 최소한 목표를 달성하는 데 도움을 줍니다. 아래에 실용적인 예시가 있습니다.

예를 들어, `Promise.all` 함수를 다시 구현하고 싶다면 이것을 `sequence`로 부르고 이것의 시그니처부터 시작하겠습니다.

```typescript
// TODO
declare function sequence<T>(
promises: Array<Promise<T>>,
): Promise<Array<T>>;
```

선언된(`declare`d) 함수는 아직 구현되지 않았지만 즉시 사용할 수 있으며 나머지 코드의 타입 검사에 사용될 수 있습니다. 하지만 빌드 시스템을 실행하는 경우 런타임에 이러한 함수가 없기 때문에 오류가 발생합니다. 현재로서는 "타입의 세계"에만 존재합니다.

그러나 이 기술의 목표는 **코드를 실행할 필요 없이 가능한 한 오랫동안 편집기에서 작업하는 것**입니다. 우리는 타입 검사기에 전적으로 의존해 오류가 발생하지 않도록 할 것입니다.

문제로 돌아가서 `A` 타입의 값 배열을 `B` 타입의 값으로 변환(또는 "축소")해야 합니다.

우리가 찾고 있는 변환은 실제로 아래와 같은 시그니처가 있는 `reduce`입니다.

```typescript
declare function reduce<A, B>(
this: Array<A>,
f: (acc: B, x: A,
) => B, init: B): B;
```

`f``init`을 모두 구현하는 방법을 아직 모르지만, 이전처럼 구체적인 구현을 건너뛸 수 있습니다. 지금은 단순히 누락된 비트를 선언(`declare`)하고 타입 매개변수 `A``B`를 우리가 작업 중인 타입으로 바꾸는 것으로 충분합니다.

```typescript
A = Promise<T>

B = Promise<Array<T>>
```

우리는 아래와 같은 결과를 얻을 수 있습니다.

```typescript
// TODO
declare function pushPromise<T>(
acc: Promise<Array<T>>,
x: Promise<T>,
): Promise<Array<T>>;

// TODO
function sequence<T>(promises: Array<Promise<T>>): Promise<Array<T>> {
declare const init: Promise<Array<T>>; // TypeScript 오류
return promises.reduce(pushPromise, init);
}
```

`declare`는 함수 본문 안에서 사용할 수 없음으로 임시적인 해결 방법이 필요합니다.

```typescript
declare const TODO: any;

// TODO
declare function pushPromise<T>(acc: Promise<Array<T>>, x: Promise<T>): Promise<Array<T>>;

// 부분적으로 구현되었다.
function sequence<T>(promises: Array<Promise<T>>): Promise<Array<T>> {
const init: Promise<Array<T>> = TODO;
return promises.reduce(pushPromise, init);
}
```

이제 `init`은 간단합니다. 빈 배열을 `Promise` "컨테이너"에 넣을 수 있습니다.

```typescript
const init: Promise<Array<T>> = Promise.resolve([]);
```

`pushPromise`를 구현하는 것은 더 복잡합니다. 우리는 `Array<T>` 타입의 값을 `T` 타입의 값과 연결해 `Array<T>` 타입의 다른 값을 얻는 방법을 알고 있습니다. 이것의 이름은 `push`로 부르겠습니다.

```typescript
declare function push<T>(x: Array<T>, y: T): Array<T>;
```

`Promise`가 모두 주어진 경우 `pushPromise`에서 `acc``x`를 어떻게 연결할 수 있을까요?

우리가 필요한 것은 프로시저이며 `liftA2`로 부르겠습니다. 이 프로시저는 `Promise` "안의" 값에 대해 작업할 수 있는 새 함수를 생성하는 함수 `push`를 "들어올릴" 수 있습니다. 다시 우리는 구현 없이 예상 결과를 선언(`declare`)합니다.

```typescript
// TODO
declare function liftA2<A, B, C>(
f: (a: A, b: B) => C,
): (fa: Promise<A>, fb: Promise<B>) => Promise<C>;

// TODO
declare function push<T>(x: Array<T>, y: T): Array<T>;

// 구현되었다.
function pushPromise<T>(acc: Promise<Array<T>>, x: Promise<T>): Promise<Array<T>> {
return liftA2<Array<T>, T, Array<T>>(push)(acc, x);
}

// 구현되었다.
function sequence<T>(promises: Array<Promise<T>>): Promise<Array<T>> {
const init: Promise<Array<T>> = Promise.resolve([]);
return promises.reduce(pushPromise, init);
}
```

이제 `liftA2``push`만 구현하면 됩니다.

```typescript
function liftA2<A, B, C>(
f: (a: A, b: B) => C
): (fa: Promise<A>, fb: Promise<B>) => Promise<C> {
return (a, b) => a.then(aa => b.then(bb => f(aa, bb)));
}

function push<T>(x: Array<T>, y: T): Array<T> {
return x.concat([y]);
}

function pushPromise<T>(acc: Promise<Array<T>>, x: Promise<T>): Promise<Array<T>> {
return liftA2<Array<T>, T, Array<T>>(push)(acc, x);
}

function sequence<T>(promises: Array<Promise<T>>): Promise<Array<T>> {
const init: Promise<Array<T>> = Promise.resolve([]);
return promises.reduce(pushPromise, init);
}
```

우리는 아래와 같이 구현한 함수를 사용해볼 수 있습니다.

```typescript
sequence([]).then((x) => console.log(x)); // []
sequence([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)],
).then((x) =>
console.log(x)
); // [1, 2, 3]
```

위에서 확인할 수 있듯이 강력한 타입 시스템은 오류를 방지할 수 있을 뿐만 아니라 설계 단계에서 사용자를 안내하고 피드백을 제공할 수 있습니다.

0 comments on commit c31567c

Please sign in to comment.