July 05, 2021
본 포스트는 fp-ts 공식 문서의 Learning Resources에 있는 Getting Started에서 소개하는 문서들을 번역하며 학습한 문서입니다. 원본 문서는 링크에서 확인할 수 있으며 작성한 코드들은 여기에서 확인할 수 있습니다.
Reader
모나드의 목적은 필요한 곳에서 인자를 얻기 위해 여러 함수를 통해 인자가 전달되는 것을 피하는 것입니다.
여기에 제시된 아이디어 중 하나는 의존성 주입을 위해 Reader
모나드를 사용하는 것입니다.
가장 먼저 알아야 할 것은 Reader<R, A>
타입이 함수 (r : R) => A
를 나타낸다는 것입니다.
interface Reader<R, A> {
(r: R): A
}
여기서 R
은 계산에 필요한 “환경”을 나타내며(여기에서 “읽을” 수 있음), A
는 계산의 결과입니다.
아래와 같은 코드가 있다고 가정해 보겠습니다.
const f = (b: boolean): string => (b ? 'true' : 'false')
const g = (n: number): string => f(n > 2)
const h = (s: string): string => g(s.length + 1)
console.log(h('foo')) // 'true'
만약 f
함수에 국제화가 필요하다면 우리는 다른 매개 변수를 추가할 수 있습니다.
interface Dependencies {
i18n: {
true: string
false: string
}
}
const f = (b: boolean, deps: Dependencies): string =>
(b ? deps.i18n.true : deps.i18n.false)
하지만 g
함수는 더 이상 컴파일되지 않는다는 문제가 발생했습니다.
const g = (n: number): string => f(n > 2)
// error: An argument for 'deps' was not provided
우리는 g
함수에도 매개변수를 추가해야 합니다.
const g = (n: number, deps: Dependencies): string =>
f(n > 2, deps) // ok
아직 끝나지 않았습니다. 이제는 h
함수가 컴파일되지 않습니다. 우리는 h
함수에도 매개변수를 추가해야 합니다.
const h = (s: string, deps: Dependencies): string =>
g(s.length + 1, deps)
마침내 우리는 Dependencies
인터페이스의 실제 인스턴스를 제공해 h
함수를 실행할 수 있습니다.
const instance: Dependencies = {
i18n: {
true: 'vero',
false: 'falso'
}
}
console.log(h('foo', instance)) // 'vero'
예시에서 볼 수 있듯이 h
와 g
함수는 f
함수의 의존성을 사용하지 않아도 의존성에 대한 지식이 있어야 합니다.
이 부분을 개선 할 수 있을까요? 네 가능합니다. Dependencies
를 매개변수 목록에서 반환 타입으로 이동할 수 있습니다.
Reader
우선 deps
매개 변수는 그대로 두고 함수를 다시 작성하겠습니다.
const f = (b: boolean): ((deps: Dependencies) => string) => deps =>
(b ? deps.i18n.true : deps.i18n.false)
const g = (n: number): ((deps: Dependencies) => string) => f(n > 2)
const h = (s: string): ((deps: Dependencies) => string) => g(s.length + 1)
참고:
(deps: Dependencies) => string
는Reader<Dependencies, string>
일 뿐입니다.
const f = (b: boolean): Reader<Dependencies, string> => (deps) =>
b ? deps.i18n.true : deps.i18n.false;
const g = (n: number): Reader<Dependencies, string> => f(n > 2);
const h = (s: string): Reader<Dependencies, string> => g(s.length + 1);
console.log(h('foo')(instance)) // 'vero'
ask
g
함수에 하한 (예시에서는 2
)도 주입하려면 어떻게 할 수 있을까요? 먼저 Dependencies
에 새 필드를 추가하겠습니다.
interface Dependencies {
i18n: {
true: string;
false: string;
};
lowerBound: number;
};
const instance: Dependencies = {
i18n: {
true: 'vero',
false: 'falso',
},
lowerBound: 2,
};
이제 우리는 ask
를 사용하여 환경에서 lowerBound
를 읽을 수 있습니다.
원문에서는
pipeable/pipe
를 사용하라고 작성되어 있지만, 최신 버전의 fp-ts에서는 deprecated 되어 있으며function/pipe
를 사용하면 됩니다.
import type { Reader } from 'fp-ts/lib/Reader';
import { pipe } from 'fp-ts/lib/function';
import { ask, chain } from 'fp-ts/lib/Reader';
const g = (n: number): Reader<Dependencies2, string> =>
pipe(
ask<Dependencies2>(),
chain((deps) => f(n > deps.lowerBound)),
);
console.log(h('foo')(instance)) // 'vero'
console.log(h('foo')({ ...instance, lowerBound: 4 })) // 'falso'
추신: Reader
의 map
은 일반적인 함수 조합 입니다.
import { flow, pipe } from 'fp-ts/lib/function'
import { map } from 'fp-ts/lib/Reader'
const len = (s: string): number => s.length
const double = (n: number): number => n * 2
const gt2 = (n: number): boolean => n > 2
const compositionWithFlow = flow(len, double, gt2)
// 아래와 같다.
const compositionWithPipe = pipe(len, map(double), map(gt2))