簡體   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