繁体   English   中英

如何在 fp-ts 中处理“else if”

[英]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样式部分在输入和输出处使用OptionEither类型。 这样做可能有助于整理一些逻辑。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM