[英]How to handle "else if" in fp-ts
很多時候我注意到我在努力如何實現一個非常簡單的流程圖,其中包含多個 if-else 條件。
如果稍后添加更多條件,此示例看起來太冗長並且無法真正擴展:
import * as O from "fp-ts/lib/Option"
type Category = {
id: string
slug: string
}
const getCategory = (category: unknown, categories: Category[]) =>
pipe(
O.fromNullable(category),
O.filter((c): c is Partial<Category> => typeof c === 'object'),
O.chain((category): O.Option<Category> => {
if (category?.id) {
return O.fromNullable(categories.find((item) => item.id === category.id))
}
if (category?.slug) {
return O.fromNullable(categories.find((item) => item.slug === category.slug))
}
return O.none
}
)
)
如果您將類別列表替換為對數據庫的調用,並且還想在 Either.left 中捕獲可能的錯誤,它甚至會變得更加復雜。
所以我的問題是:我們應該如何處理 fp-ts 中的一個或多個“else if”語句?
一個可能有用的函數是alt
,它指定一個 thunk,如果pipe
中的第一件事是 none 則生成一個選項,否則不運行。 使用alt
,您的第一個示例變為:
import * as O from "fp-ts/Option";
import { pipe } from "fp-ts/function";
interface Category {
id: string;
slug: string;
}
declare const categories: Category[];
function getCategory(category: string | null, slug: string | null) {
const cat = O.fromNullable(category);
const s = O.fromNullable(slug);
return pipe(
cat,
O.chain((id) => O.fromNullable(categories.find((c) => c.id === id))),
O.alt(() =>
pipe(
s,
O.chain((someSlug) =>
O.fromNullable(categories.find((c) => c.slug === someSlug))
)
)
)
);
}
我注意到的一件事是您正在根據 if type === "object"
進行過濾。 我不確定這是否是為了簡化實際代碼正在做的事情,但如果你還沒有這樣做的話,我建議你使用像io-ts
這樣的庫。
Either
還有一個alt
的實現,只有在它之前的東西是Left
時才會運行。
我還發現使用fromNullable
有點麻煩,並嘗試將我的代碼fp-ts
-y 的fp-ts
樣式部分在輸入和輸出處使用Option
和Either
類型。 這樣做可能有助於整理一些邏輯。
Souperman 建議使用alt
有效,但是一旦您開始涉及其他類型(例如Either
),可能會變得有點復雜。
您可以使用O.match
(或相同的O.fold
)在第二個流程圖中實現場景:
import * as E from "fp-ts/lib/Either"
import * as O from "fp-ts/lib/Option"
import {pipe} from "fp-ts/lib/function"
type Category = {
id: string
slug: string
}
// Functions to retrieve the category from the database
declare const getCategoryById: (id: string) => E.Either<Error, O.Option<Category>>
declare const getCategoryBySlug: (slug: string) => E.Either<Error, O.Option<Category>>
const getCategory = (category: unknown): E.Either<Error, O.Option<Category>> =>
pipe(
O.fromNullable(category),
O.filter((c): c is Partial<Category> => typeof c === "object"),
O.match(
// If it's None, return Right(None)
() => E.right(O.none),
// If it's Some(category)...
category =>
// Retrieve the category from the database
category?.id ? getCategoryById(category.id) :
category?.slug ? getCategoryBySlug(category.slug) :
// If there's no id or slug, return Right(None)
E.right(O.none)
)
)
在這種情況下,我不會通過嘗試“強制”一個 fp-ts 解決方案來使事情復雜化。 您可以通過使用三元運算符大大簡化您的邏輯:
declare const getById: (id: string) => Option<Category>
declare const getBySlug: (slug: string) => Option<Category>
const result: Option<Category> = id ? getById(id) : getBySlug(slug)
不需要復雜的可選內容鏈接。 如果您將各種管道步驟剝離為短函數,然后將這些函數名稱放入管道中,您會發現邏輯不需要那么復雜,只是作為使用 monad 的借口。
盡管如果這確實是一回事,您也可以這樣做:
const getter: (arg: Either<Id, Slug>) => Option<Category> = E.fold(getById, getBySlug)
Either
只是用於處理錯誤。 它還用於建模任何互斥的非此即彼的場景。 只需將 Left 或 Right 傳入該函數即可。 這樣代碼就短了很多,作為獎勵,它是使用 monad 的借口!
和 Souperman 一樣,我真的很喜歡這里的alt
和 user1713450,我也喜歡這里的io-ts
。 即使輸入unknown
,我們也可以定義我們關心的內容並針對它進行編碼。 我真正喜歡alt
的一件事是當我們需要添加更多條件時它的靈活性。 假設您想檢查一個新屬性,那么您只需添加新的 alt。 getCategory
function 保持非常可讀。
import * as O from 'fp-ts/Option'
import {pipe} from 'fp-ts/function'
import * as t from 'io-ts'
import * as A from 'fp-ts/Array'
type Category = {
id: string
slug: string
}
const PossibleCategory = t.union([
t.partial({
id:t.string,
slug:t.string
}),
t.undefined])
type PossibleCategory = t.TypeOf<typeof PossibleCategory>
const getCategory = (possibleCategory: PossibleCategory, categories: Category[]) => pipe(
categoryById(possibleCategory, categories),
O.alt(() => categoryBySlug(possibleCategory, categories))
)
const categoryById = (possibleCategory: PossibleCategory, categories: Category[]):O.Option<Category> => pipe(
O.fromNullable(possibleCategory?.id),
O.chain(id => pipe(categories, A.findFirst(c => c.id === id)))
)
const categoryBySlug = (possibleCategory: PossibleCategory, categories: Category[]): O.Option<Category> => pipe(
O.fromNullable(possibleCategory?.slug),
O.chain(slug => pipe(categories, A.findFirst(c => c.slug === slug)))
)
第二種情況確實使getCategory
function 的可讀性降低了一些。 正如cherryblossum 所提到的,它采用fold
路線。
import * as O from 'fp-ts/Option'
import {pipe, flow, identity} from 'fp-ts/function'
import * as t from 'io-ts'
import * as E from 'fp-ts/Either'
type Category = {
id: string
slug: string
}
const PossibleCategory = t.union([
t.partial({
id:t.string,
slug:t.string
}),
t.undefined])
type PossibleCategory = t.TypeOf<typeof PossibleCategory>
type GetCategory = (x:string) => E.Either<Error, O.Option<Category>>
// placeholders for db calls
const getCategoryById:GetCategory = (x:string) => E.right(O.none)
const getCategoryBySlug:GetCategory = (x:string) => E.right(O.none)
declare const categories: Category[];
const getCategory = (possibleCategory: PossibleCategory) => pipe(
categoryById(possibleCategory),
E.chain(
O.fold(
() => categoryBySlug(possibleCategory),
c => E.right(O.some(c))
)
)
)
const categoryById = (possibleCategory: PossibleCategory) => pipe(
O.fromNullable(possibleCategory?.id),
O.map(
flow(
getCategoryById,
E.chainOptionK(() => new Error('id not found'))(identity),
)
),
O.sequence(E.Monad),
)
const categoryBySlug = (possibleCategory:PossibleCategory)=> pipe(
O.fromNullable(possibleCategory?.slug),
O.map(
flow(
getCategoryBySlug,
E.chainOptionK(() => new Error('slug not found'))(identity),
)
),
O.sequence(E.Monad)
)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.