今天要從頭開始打造一個Monad類別的Response模組,將各類別的定義複習一次,以後再看到這些FP的類別名詞時就不會再陌生。
依照昨天的內容定義常數URI和型別URI皆為'Response',並定義 Response 型別容器,容器中由型別參數決定型別的屬性為body。
// 類別介面模組匯入
import { Kind, URIS } from 'fp-ts/HKT';
import { Pointed1 } from 'fp-ts/Pointed';
import { Functor1 } from 'fp-ts/Functor';
import { Apply1 } from 'fp-ts/Apply';
import { Applicative1 } from 'fp-ts/Applicative';
import { Chain1 } from 'fp-ts/Chain';
import { Monad1 } from 'fp-ts/Monad';
import { Foldable1 } from 'fp-ts/Foldable';
import { Traversable1 } from 'fp-ts/Traversable';
import { Monoid } from 'fp-ts/Monoid';
// 1. 定義 URI
export const URI = 'Response';
export type URI = typeof URI;
// 2. 擴展 fp-ts 的 URI 映射
declare module 'fp-ts/HKT' {
interface URItoKind<A> {
readonly [URI]: Response<A>;
}
}
// 3. 定義 Response 型別容器
export interface Response<A> {
readonly _tag: URI;
readonly url: string;
readonly status: number;
readonly headers: Record<string, string>; // 物件型別
readonly body: A;
}
// 4. 通用構造函數
export const make = <A>(
url: string,
status: number,
headers: Record<string, string>,
body: A
): Response<A> => ({
_tag: URI,
url,
status,
headers,
body,
});
接下來建構Monad類別所需的函數(of, map, ap, flatten, flatMap)
export const of = <A>(body: A): Response<A> => make('', 200, {}, body);
export const map =
<A, B>(f: (a: A) => B) =>
(fa: Response<A>): Response<B> =>
make(fa.url, fa.status, fa.headers, f(fa.body));
export const ap =
<A>(fa: Response<A>) =>
<B>(fab: Response<(a: A) => B>): Response<B> =>
make(fa.url, fa.status, fa.headers, fab.body(fa.body));
export const flatten = <A>(ffa: Response<Response<A>>): Response<A> => ({
...ffa, // 優先選擇外層url, status
headers: { ...ffa.headers, ...ffa.body.headers }, // 合併內外層headers
body: ffa.body.body,
});
const flatMap =
<A, B>(f: (a: A) => Response<B>) =>
(fa: Response<A>): Response<B> =>
flatten(map(f)(fa));
const chain = flatMap;
再來建立Pointed、Functor、Apply、Applicative和Monad的類別實例。
// Pointed: of
export const Pointed: Pointed1<URI> = {
URI,
of,
};
// Functor: map
export const Functor: Functor1<URI> = {
URI,
map: (fa, f) => map(f)(fa),
};
const double = (x: number) => 2 * x;
const tripleR = (x: number) => RE.of(3 * x);
const add2 = (x: number) => (y: number) => x + y;
console.log(pipe(RE.of(5), RE.map(double), RE.flatMap(tripleR))); // RE.of(30)
console.log(pipe(RE.of(add2), RE.ap(RE.of(3)), RE.ap(RE.of(4)))); // RE.of(7)
// Apply: map, ap
export const Apply: Apply1<URI> = {
...Functor,
ap: (fab, fa) => ap(fa)(fab),
};
// Applicative: of, map, ap
export const Applicative: Applicative1<URI> = {
...Apply,
of,
};
// Chain: map, ap, chain(flatMap)
export const Chain: Chain1<URI> = {
...Apply,
chain: (fa, f) => chain(f)(fa),
};
// Monad: of, map, ap, chain
export const Monad: Monad1<URI> = {
...Applicative,
chain: (fa, f) => chain(f)(fa),
};
Functor的map函數必須遵守下列兩個規則:
Apply中的ap也必須滿足下列規則:
Applicative除了滿足Apply的規則之外,還有滿足下列三個規則:
Chain除了滿足Apply的規則之外,還有滿足下面的規則:
Monad除了滿足Applicative和Chain的規則之外,還有滿足下列二個規則:
最後再完成和Traversable有關的函數和介面。
export const reduce =
<A, B>(b: B, f: (b: B, a: A) => B) =>
(fa: Response<A>): B =>
f(b, fa.body);
export const foldMap =
<B>(m: Monoid<B>) =>
<A>(f: (a: A) => B) =>
(fa: Response<A>): B =>
f(fa.body);
export const reduceRight =
<A, B>(b: B, f: (a: A, b: B) => B) =>
(fa: Response<A>): B =>
f(fa.body, b);
// Foldable: reduce, reduceRight, foldMap
export const Foldable: Foldable1<URI> = {
URI,
reduce: (fa, b, f) => reduce(b, f)(fa),
foldMap: (M) => (fa, f) => foldMap(M)(f)(fa),
reduceRight: (fa, b, f) => reduceRight(b, f)(fa),
};
export const traverse =
<F extends URIS>(Ap: Applicative1<F>) =>
<A, B>(f: (a: A) => Kind<F, B>) =>
(ta: Response<A>): Kind<F, Response<B>> =>
Ap.map(f(ta.body), (b: B) => make(ta.url, ta.status, ta.headers, b));
export const sequence =
<F extends URIS>(Ap: Applicative1<F>) =>
<A>(ta: Response<Kind<F, A>>): Kind<F, Response<A>> =>
Ap.map(ta.body, (a: A) => make(ta.url, ta.status, ta.headers, a));
// Traversable: map, reduce, reduceRight, foldMap, traverse, sequence
export const Traversable: Traversable1<URI> = {
...Functor,
...Foldable,
traverse:
<F extends URIS>(Ap: Applicative1<F>) =>
<A, B>(ta: Response<A>, f: (a: A) => Kind<F, B>) =>
traverse(Ap)(f)(ta),
sequence,
};
sequence和traverse函數從下列兩個式子中可以視為功能相容的函數:
今天用極其簡單的例子打造一個Response模組,主要的目的是複習所有FP類別介面所需要的函數,大家可以將今天的內容當作字典查詢,不需要反覆翻閱各類別介面的規定。今天內容就分享到這邊,明天再見。