[英]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.