fp-ts中有兩個模塊:
- 選項
- 選項T
正如代碼約定一章所說
但是通常它意味著像“monad 轉換器”中的轉換器(例如OptionT、EitherT、ReaderT、StateT)
那么什么是變壓器?我怎么知道要使用什么匯入?
歡迎舉例。
uj5u.com熱心網友回復:
讓我們看看什么Option和OptionT是。
export type Option<A> = None | Some<A>
/** @deprecated */
export interface OptionT<M, A> extends HKT<M, Option<A>> {}
Option是我們熟悉的base monad,代表a Some<A>or None。
OptionT<M, A>與 相同HKT<M, Option<A>>。這個介面實際上已經被棄用了——我稍后會詳細解釋。但是,您仍然可以在OptionT不使用OptionT型別的情況下使用模塊中的輔助函式。
你可以這樣想OptionT(遺憾的是,由于 TypeScript 缺乏更高種類的型別,它無法編譯):
type OptionT<M, A> = M<Option<A>>
OptionT是一個單子變壓器的例子。monad 轉換器是一種將多個 monad 組合成一個新 monad 的方法,以便我們可以使用chain(和其他實用程式功能)。
例子
讓我們看一個(稍微做作的)例子。
import * as Console from 'fp-ts/Console'
import * as IO from 'fp-ts/IO'
import * as O from 'fp-ts/Option'
import {pipe} from 'fp-ts/function'
type IOOption<A> = IO.IO<O.Option<A>> // our new monad
// You could think of IOOption<A> as OptionT<IO, A>
const getNumber: IOOption<number> = pipe(
Console.log('getting number'),
IO.map(() => O.some(42))
)
/** Returns None when the number is not divisible by 2. */
const half = (number: number): IOOption<number> =>
pipe(
Console.log('halving number'),
IO.map(() => (number % 2 ? O.none : O.some(number / 2)))
)
我們如何把half和getNumber放在一起?最初,您可能會考慮做這樣的事情:
const bad: IOOption<number> = pipe(getNumber, IO.chain(O.chain(half)))
// ~~~~
// Argument of type '(number: number) => IOOption<number>' is not
// assignable to parameter of type '(a: number) => Option<unknown>'.
但是,這不起作用。O.chain接受一個函式,該函式回傳一個Option,而不是一個IOOption. 此外,IO.chain接受一個回傳 的函式IO,但O.chain回傳一個回傳 的函式Option。(有關更多資訊,您可以閱讀有關monad 如何不組成的資訊。)
正確的方法是:
const good = pipe(
getNumber,
IO.chain(O.fold(
// If we get a None, return IO None
() => IO.of(O.none),
// If we get a Some(number), return half(number)
half
))
)
對于這個小例子來說這似乎很好,但是當你將越來越多的函式鏈接在一起時,你可以想象這會變得有點笨拙:
pipe(
foo,
IO.chain(O.fold(() => IO.of(O.none), bar)),
IO.chain(O.fold(() => IO.of(O.none), baz)),
IO.chain(O.fold(() => IO.of(O.none), qux))
)
幸運的是,我們有OptionT模塊。使用chain這個模塊中的,我們可以洗掉IO.chain(O.fold(() => IO.of(O.none), ...))樣板:
import * as OT from 'fp-ts/lib/OptionT'
const better = pipe(getNumber, OT.chain(IO.Monad)(half))
const ioOptionChain = OT.chain(IO.Monad)
const alsoBetter = pipe(getNumber, ioOptionChain(half))
的型別OT.chain是這樣的:
// chain :: Monad m => (a -> m (Option b)) -> m (Option a) -> m (Option b)
export declare function chain<M>(
M: Monad<M>
): <A, B>(f: (a: A) => HKT<M, Option<B>>) => (ma: HKT<M, Option<A>>) => HKT<M, Option<B>>
// a bunch of other overloads omitted for brevity
您可能已經注意到我們使用的方法good可以推廣到任何 monad M:
M.chain(O.fold(() => M.of(O.none), fn))
這就是為什么OT.chain接受 a M: Monad<M>: monad 實體,例如IO.Monad, Task.Monad,Either.Monad等。
我使用OptionorOptionT嗎?
如果Option您只處理 Option. 如果包裹在其他型別中,如, , ,等,OptionT則使用。OptionIOTaskArrayEither
為什么OptionT被棄用
回想一下,OptionT<M, A>等價于HKT<M, Option<A>>。雖然您會看到HKT一些實用函式的多載彈出視窗,但您可能不會在自己的型別中使用它。例如,我們IOOption<A> 的不是 HKT<'IO', A>,而是等價于Kind<'IO', Option<A>>.
但是,Kind僅適用于具有 1 個型別引數的 monad。對于更多型別引數,有Kind2(例如 for Either)、Kind3(例如 for ReaderEither)和Kind4(例如 for StateReaderTaskEither)。
這就是為什么有這么多的多載 forOT.chain和其他類似的功能:
export declare function chain<M extends URIS4>(M: Monad4<M>): <A, S, R, E, B>(f: (a: A) => Kind4<M, S, R, E, Option<B>>) => (ma: Kind4<M, S, R, E, Option<A>>) => Kind4<M, S, R, E, Option<B>>
export declare function chain<M extends URIS3>(M: Monad3<M>): <A, R, E, B>(f: (a: A) => Kind3<M, R, E, Option<B>>) => (ma: Kind3<M, R, E, Option<A>>) => Kind3<M, R, E, Option<B>>
export declare function chain<M extends URIS3, E>(M: Monad3C<M, E>): <A, R, B>(f: (a: A) => Kind3<M, R, E, Option<B>>) => (ma: Kind3<M, R, E, Option<A>>) => Kind3<M, R, E, Option<B>>
export declare function chain<M extends URIS2>(M: Monad2<M>): <A, E, B>(f: (a: A) => Kind2<M, E, Option<B>>) => (ma: Kind2<M, E, Option<A>>) => Kind2<M, E, Option<B>>
export declare function chain<M extends URIS2, E>(M: Monad2C<M, E>): <A, B>(f: (a: A) => Kind2<M, E, Option<B>>) => (ma: Kind2<M, E, Option<A>>) => Kind2<M, E, Option<B>>
export declare function chain<M extends URIS>(M: Monad1<M>): <A, B>(f: (a: A) => Kind<M, Option<B>>) => (ma: Kind<M, Option<A>>) => Kind<M, Option<B>>
export declare function chain<M>(M: Monad<M>): <A, B>(f: (a: A) => HKT<M, Option<B>>) => (ma: HKT<M, Option<A>>) => HKT<M, Option<B>>
我們實際使用的多載OT.chain(IO.Monad)是其中的多載Kind(倒數第二個)。
OptionT這就是為什么當它只能用于最后一個多載時使用型別并沒有多大意義HKT。
如果您想了解有關 monad 轉換器的更多資訊:
- Haskell wiki 上的“Monad 轉換器”
- monad轉換器的使用 哈斯克爾
即使它們是關于 Haskell 的,同樣的原則也適用于 fp-ts。
uj5u.com熱心網友回復:
Monad Transformer 是一個來自函式式編程的概念。我將遵循 Wikipedia 的定義,因為它看起來非常精確:
在函式式編程中,monad 轉換器是一個型別建構式,它接受一個 monad 作為引數并回傳一個 monad 作為結果。
Monad 轉換器可用于以模塊化方式組合由 monad 封裝的功能,例如狀態、例外處理和 I/O。通常,monad 轉換器是通過推廣現有的 monad 來創建的。將生成的 monad 轉換器應用于身份 monad 會產生一個等效于原始 monad 的 monad(忽略任何必要的裝箱和拆箱)。
——維基百科
為了幫助說明為什么這可能是一組很好的實用程式,讓我們看一下模塊中的match定義OptionT。
// It is overloaded a few time to support Functors with multiple type parameters
// but this is the simplest
export declare function match<F>(
F: Functor<F>
): <B, A>(onNone: () => B, onSome: (a: A) => B) => (ma: HKT<F, Option<A>>) => HKT<F, B>
如果您有 aFunctor并且希望能夠處理該函子Option僅保留一次值的情況,您將使用此函式。
例如,如果您Functor是 anArray那么您可以使用matchlike
import * as A from 'fp-ts/Array';
import * as O from 'fp-ts/Option';
import { match } from 'fp-ts/OptionT';
import { pipe } from 'fp-ts/function';
const defaultTo0 = match(
A.Functor,
)(
() => 0,
(a: number) => a,
);
const sum = pipe(
[O.some(1), O.some(2), O.none, O.some(3)],
defaultTo0,
A.reduce(0, (acc, c) => acc c),
);
console.log(sum); // 6
在這種情況下,我們使用match選項轉換器模塊中的函式來創建一個轉換Array<Option<number>>為Array<number>. Monad Transformers 是一個相當廣泛的主題,所以很難用比維基百科文章所說的更明確的術語說“這正是你使用它的時候”。希望這個例子提供了一個小例子,說明它如何讓管理多個 monadic 層變得不那么乏味?
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/482443.html
