0

What's the best way to remove duplicates of an array of Either in Functional Programming using fp-ts?

This is my attemp:

import { either as E, pipeable as P } from "fp-ts";
import { flow } from "fp-ts/lib/function";

interface IItem {
  type: "VALID" | "INVALID";
  value: string;
}

// Building some fake data
const buildItem = (value?: string): E.Either<unknown, string> =>
  value != null ? E.right(value) : E.left({ type: "INVALID", value: "" });

// We will always have an array of Either
const items = [
  buildItem("aa"),
  buildItem("ab"),
  buildItem(),
  buildItem("ac"),
  buildItem("ab"),
  buildItem("ac"),
  buildItem(),
  buildItem("aa")
];

const checkList: string[] = [];
export const program = flow(
  () => items,
  x =>
    x.reduce(
      (acc, item) =>
        P.pipe(
          item,
          E.chain(value => {
            if (checkList.indexOf(value) < 0) {
              checkList.push(value);
              return E.right({ type: "VALID", value: value } as IItem);
            }
            return E.left({ type: "INVALID", value: value } as IItem);
          }),
          v => acc.concat(v)
        ),
      [] as E.Either<unknown, IItem>[]
    )
);

Playground link

Amin Paks
  • 276
  • 2
  • 15
  • What about using `.filter()` to get rid of duplicates and `.map()` over the rest to return a new formed list? – weirdpanda Apr 19 '20 at 08:07
  • I'm expecting to use some sort of Either or Option here. I guess I have to update the question with those. – Amin Paks Apr 19 '20 at 08:08
  • Ah okay, got it! I can give you a basic place to start in pure JS if it works? – weirdpanda Apr 19 '20 at 08:12
  • Sure @weirdpanda go ahead. I will try to update the question for better visibility and clarification. – Amin Paks Apr 19 '20 at 08:13
  • @AminPaks, it's not entirely clear how the code snippets above relate to the question you asked. I think things my be clearer if you remove the code and ask the question using only types rather than implementations. It seems like `uniq(eqString)(items.filter(notNull))` is all you really need, but your question asks about reduce. Reduce doesn't seem necessary for what you're doing. – Derrick Beining Apr 24 '20 at 16:34
  • @DerrickBeining would you write your answer in a valid code? Remember we have an array of `Either` or `Option`, that's required from my implementation – Amin Paks Apr 24 '20 at 17:57
  • @AminPaks I'm saying using reduce and Either doesn't seem necessary given the code snippet you posted. – Derrick Beining Apr 24 '20 at 17:59
  • @AminPaks It looks like all you're trying to do is remove `null`s and duplicates from `items`. You don't need reduce or Either for that: https://codesandbox.io/s/reduce-array-in-fp-ts-ohlb4?file=/src/index.ts – Derrick Beining Apr 24 '20 at 18:05
  • @DerrickBeining well as I already said the requirement of the question is that the input is an array of `Either` or `Option`. I didn't build the question properly I guess. Let me update the question – Amin Paks Apr 24 '20 at 18:16
  • @AminPaks the input to what? Try re-phrasing the question like this: "Using `fp-ts`, how can I implement a function that takes a `Foo` and returns a `Bar` and fulfills the following requirements: 1) ... 2) ... – Derrick Beining Apr 24 '20 at 18:24
  • @DerrickBeining Is this not yet clear enough "Remove duplications of an array of Either or Option"? – Amin Paks Apr 24 '20 at 18:26
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/212439/discussion-between-derrick-beining-and-amin-paks). – Derrick Beining Apr 24 '20 at 18:27

1 Answers1

2

In general, in fp-ts, you can remove duplicates from an Array<A> by using uniq from fp-ts/lib/Array. Given an Eq<A> and an Array<A> it will return an Array<A> where all As are unique.

In your case, it appears you want de-duplicate an Array<Either<IItem, IItem>>. That means, in order to use uniq, you are going to need an instance of Eq<Either<IItem, IItem>>. The way you get that is to use getEq from fp-ts/lib/Either. It requires you to provide an Eq instance for each type your Either is parameterized by, one for the left case and another for the right case. So for Either<E, R>, getEq will take an Eq<E> and an Eq<R> and give you an Eq<Either<E, R>>. In your situation, E and R are the same (i.e. IItem), so you just use the same Eq<IItem> instance twice.

Most likely, the instance of Eq<IItem> you want will look like this:

// IItem.ts |
//-----------
import { Eq, contramap, getStructEq, eqString } from 'fp-ts/lib/Eq'

export interface IItem {
  type: "VALID" | "INVALID";
  value: string;
}

export const eqIItem: Eq<IItem> = getStructEq({
  type: contramap((t: "VALID" | "INVALID"): string => t)(eqString),
  value: eqString
})

Once you have that, you can then deduplicate your Array<Either<IItem, IItem>> with uniq like this:

// elsewhere.ts |
//---------------
import { array, either } from 'fp-ts'
import { Either } from 'fp-ts/lib/Either'
import { IItem, eqIItem } from './IItem.ts'

const items: Array<Either<IItem, IItem>> = []

const uniqItems = uniq(either.getEq(eqIItem, eqIItem))(items)

The uniqItems constant will then be an Array<Either<IItem, IItem>> where no two Either<IItem, IItem> are "equal" as defined by Eq<Either<IItem, IItem>>.

Derrick Beining
  • 876
  • 8
  • 15