在本系列文中,所有的程式碼以及測試都可以在 should-i-use-fp-ts 找到,今日的範例放在 src/day-14
並且有習題和測試可以讓大家練習。
前幾天我們說到如何針對單個變數使用 Option
來進行操作,那需要操作多個變數的情況要如何處理?要如何暫時儲存變數?
這邊會使用到 Functoinal Programming
中著名的 Do notation
,雖然在 javascript
當中的實作並不是那麼的優雅簡潔,但還是可以學習,首先我們看一下 O.Do
的建構子。
type DoType = O.Option<object>;
export const Do: DoType = O.of({});
沒錯,在 fp-ts
中 Do
的實作是將使用到的變數儲存在一個 object
裡面,接著來看使用方法。
// use x=1, y=2 as example
const bindExample = (x: number, y: number) => pipe(
O.Do, // { _tag: 'Some', value: {} }
O.bind('x', () => O.of(x * 2 + 3)), // { _tag: 'Some', value: { x: 5 } }
// bind's second fn can use the value from Do.
O.bind('y', ({ x }) => O.of(y + x * 3)), // { _tag: 'Some', value: { x: 5, y: 17 } }
O.map(({ x, y }) => x + y), // { _tag: 'Some', value: 22 }
O.getOrElse(() => 0), // 22
);
首先使用 O.Do
來初始化 Do notation ({})
,接著使用 O.bind
來 append 需要的屬性到 Do notation
之中,O.bind
首先接受 property
的名字(key
),然後接受一個將要輸入的值,這邊需要注意的是,在使用 O.bind
的期間,我們可以從 callback function
中來使用目前 Do notation
中的屬性。
接著我們來嘗試 bind
的實作,首先從 type
開始。
type Bind =
<N extends string, A, B>(name: Exclude<N, keyof A>, f: (a: A) => O.Option<B>)
=> (ma: O.Option<A>)
=> O.Option<{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }>;
如果有些人對於 Typescript
不太熟悉的話可能會看不太懂,簡單來說就是傳入 (name, f
) 前者是屬性名稱,後者則是運算出屬性結果的函式,接著接收(ma
) 同時也是我們的 Do notation
本體,最終結果是新增 屬性 name
的 ma
。
接下來是實際的實作,會用到之前學習過的函式。
const bind: Bind = (name, f) => ma => pipe(
ma,
O.flatMap(a => pipe(
f(a),
O.map(b => Object.assign({}, a, { [name]: b }) as any),
)),
);
藉於 ma: O.Do
本身就是 Option<A>
,這邊需要使用 O.flatMap
來進行操作避免 nested Option
,然後使用 f(a)
運算出要注入 Do
的函數,此時的型別會是 Option<B>
,所以要使用 O.map
來進行運算,最後使用 Object
的 assign
來將兩者結合即可。
今天的主題在 should-i-use-fp-ts src/day-14
有習題和測試可以練習,大家可以嘗試自己能不能寫出自己的 Do
, bind
。