mobily / ts-belt

🔧 Fast, modern, and practical utility library for FP in TypeScript.
https://mobily.github.io/ts-belt
MIT License
1.08k stars 30 forks source link

Can you tell me how to handle multiple Option types? #100

Closed araera111 closed 6 months ago

araera111 commented 6 months ago

I love this library.

Can you tell me how to handle multiple Option types?

Assume there are multiple Option type values. el1 = string, el2=boolean, el3=number, el4=number[].

In this case, I want to take out the value of el1 to el4 when all of el1 to el4 are Some. if one of el1 to el4 is None, I want to process it as None.

The following is not the correct syntax, but is what I want to do.

match([el1, el2, el3, el4]) => {
Some(el1), Some(el2), Some(el3), Some(el4) => {
// el1 = string, el2 = boolean, el3 = number, el4 = number[]
}
_ => {error}
}

Below is my silly solution that I thought about for 30 minutes

const el1: O.Option<string> = O.Some("3");
const el2: O.Option<boolean> = O.Some(true);
const el3: O.Option<number> = O.Some(4);
const el4: O.Option<string[]> = O.Some(["hello"]);
const optionsResult = O.flatMap(el1, (el1) =>
  O.flatMap(el2, (el2) =>
    O.flatMap(el3, (el3) => O.flatMap(el4, (el4) => ({ el1, el2, el3, el4 }))),
  ),
);
JUSTIVE commented 6 months ago

I'd prefer ts-pattern for these scenarios.

const el1: O.Option<string> = O.Some("3");
const el2: O.Option<boolean> = O.Some(true);
const el3: O.Option<number> = O.Some(4);
const el4: O.Option<string[]> = O.Some(["hello"]);

const optionsResult = 
  match([el1,el2,el3,el4])
  .with([P.not(P.nullish),P.not(P.nullish),P.not(P.nullish),P.not(P.nullish)],
    ([el1,el2,el3,el4])=>({el1,el2,el3,el4}))
  .otherwise(F.always(O.None))

I've experienced the same dx issue in the rescript. When handling >3 option types, using pattern matching was much better to read.

in other languages such as purescript or haskell, they have do-notation for this but unfortunately, javascript does not.

araera111 commented 6 months ago

I am also a fan of ts-pattern.

I did as you described and it seems to be working well. Thanks for your help, I appreciate it.

I thought of a utility function during my lunch break. The following is not type safe as it uses as assertion.

const el1: O.Option<string> = O.Some("hello");
const el2: O.Option<boolean> = O.Some(true);
const el3: O.Option<number> = O.Some(4);
const el4: O.Option<string[]> = O.Some(["hello", "world"]);

type OptionObject<T> = {
  [K in keyof T]: Option<T[K]>;
};

const zipAllOption = <T>(
  options: OptionObject<T>,
): Option<{ [K in keyof T]: T[K] }> =>
  pipe(
    options,
    D.toPairs,
    A.every(([_, v]) => O.isSome(v)),
    B.ifElse(
      () => options as { [K in keyof T]: T[K] },
      () => O.None,
    ),
  );

const optionResult = zipAllOption({ el1, el2, el3, el4 });
JUSTIVE commented 6 months ago

I've just found O.all from ts-belt 4.0.0.

https://github.com/mobily/ts-belt/blob/c825d9709de1e5d15b1b362429e0cee56c96c516/src/Option/index.ts#L213C1-L215C32

const el1: O.Option<string> = O.Some("3");
const el2: O.Option<boolean> = O.Some(true);
const el3: O.Option<number> = O.Some(4);
const el4: O.Option<string[]> = O.Some(["hello"]);

const result = 
  pipe(
    [el1,el2,el3,el4],
    O.all,
    O.map(([el1,el2,el3,el4])=> /* your function goes here */), 
  )

This is probably the right function for you.

araera111 commented 6 months ago

Thanks!