Удаление параметров из массива элементов в fp-ts

#typescript #functional-programming #fp-ts

#typescript #функциональное программирование #fp-ts

Вопрос:

Допустим, у меня есть следующие типы:

 type ValidatedInput = Readonly<{
  id: string
}>

type GetStructures = (id: string) => TaskEither<ServiceError, ReadonlyArray<SomeStructure>>

type GetOtherStructures = (ids: ReadonlyArray<string>) => TaskEither<ServiceError, ReadonlyArray<SomeOtherStructure>>

type LookupThings = (initialInput: ReadonlyArray<SomeStructure>, additionalInput: ReadonlyArray<SomeOtherStructure>) => TaskEither<ServiceError, ReadonlyArray<Option<ResponseType>>>

export type Deps = Readonly<{
  getStructures: GetStructures
  getOtherStructures: GetOtherStructures
  lookupThings: LookupThings
}>

export type Ctx<T> = ReaderTaskEither<Deps, Error, T>
 

И следующий помощник по обработке ошибок:

 type Code = 'INVALID_ENTITY' | 'INVALID_API_KEY'
export const fromCode = (code: Code): Error => ({
  tag: 'validation',
  code
})
 

Затем я создаю функцию следующим образом:

 const constructFullData = (input: ValidatedInput): Ctx<ReadonlyArray<Option<ResponseType>>> => (
  (deps: Deps): TE.TaskEither<Error, ReadonlyArray<Option<ResponseType>>> => (
    pipe(
      deps.getStructures(input.id),
      TE.map((structs) => pipe(
        deps.getOtherStructures([structs[0].higherOrderId]),
        TE.chain(otherStructs => pipe(
          deps.lookupThings(structs, otherStructs),
          TE.chain(results => {
            if (results.filter(isNone).length !== results.length) {
              return TE.left(fromCode('INVALID_ENTITY')) // I'm not sure what a better way to filter this is. Ideally the return type of this  function wouldn't have an Option in it
            } else {
              return TE.right(results)
            }
          })
        ))
      )),
      TE.flatten
    )
  )
)
 

Все это компилируется просто отлично, но чего я действительно хочу, так это вернуть массив с отфильтрованным none и вызвать соответствующую ошибку, если нет none!

Наивный подход заключается в изменении возврата правильного пути на:

 return TE.right(results.map(u => u.value))
 

Но это не компилируется, выдавая следующую ошибку:

 Property 'value' does not exist on type 'None'.
64               return TE.right(usagesWithDrones.map(u => u.value))
 

Как можно применить такую фильтрацию?

Комментарии:

1. Поправьте меня, если я ошибаюсь, но «что я действительно хочу, так это вернуть массив, в котором ни один не отфильтрован, и вызвать соответствующую ошибку, если есть none» означает, что вы хотите вернуть исходный массив (если они все некоторые) или вернуть ошибку, если естьни одного. Для этого fp-ts/Array имеет sequence : предполагая, что x равно Option<number>[] , тогда Array.sequence(option)(x) будет выдано Some<number[]> , если все они являются некоторыми или None если даже один из них равен None .

Ответ №1:

Как насчет сворачивания списка параметров в either? Что-то вроде:

 import * as TE from "fp-ts/TaskEither";
import * as ROA from "fp-ts/ReadonlyArray";
import * as T from "fp-ts/Task";
import * as E from "fp-ts/Either";
import * as O from "fp-ts/Option";
import { pipe } from "fp-ts/pipeable";
import { constant, flow, Lazy } from "fp-ts/lib/function";

const value = TE.right<"ThisErrorDoesntExist", readonly O.Option<number>[]>(
  ROA.of(O.some(42))
);

const arrayOfNumbersMonoid = ROA.getMonoid<number>();

type Result = E.Either<"FoundNoneInList", readonly number[]>;

const rightEmptyListOfNumbers: Result = E.right([]);

const leftFoundNoneInList: Lazy<Result> = constant(E.left("FoundNoneInList"));

const reducer = (acc: typeof rightEmptyListOfNumbers, next: O.Option<number>) =>
  pipe(
    acc,
    E.chain((numbers) =>
      pipe(
        next,
        O.fold(
          leftFoundNoneInList,
          flow(ROA.of, (n) => arrayOfNumbersMonoid.concat(numbers, n), E.right)
        )
      )
    )
  );

const result = pipe(
  value,
  T.map(E.chainW(ROA.reduce(rightEmptyListOfNumbers, reducer)))
);

 

Ответ №2:

Самый простой способ удалить параметры из массива — использовать функцию compact (см. Простой пример ниже). Функция compact присутствует в большинстве библиотек fp-ts.

 import { Option, some, none } from "fp-ts/lib/Option"
import { compact } from "fp-ts/lib/Array"

const initialArray = [1, 2, 3, 4, 5, 6, 7]

const arrayWithOptions: Option<number>[] = initialArray.map((el) => {
  if (el % 2 === 0) return some(el)
  else return none
})

const finalArray: number[] = compact(arrayWithOptions)

console.log(finalArray)
 

В журнале консоли будет напечатано [2,4,6].