sweet-monads icon indicating copy to clipboard operation
sweet-monads copied to clipboard

Possible library improuvements

Open AlexXanderGrib opened this issue 1 year ago • 2 comments

Hello, while @JSMonk was away this spring i created my fork of this library. Here it is https://github.com/AlexXanderGrib/monads-io

And here some good ideas you can grab from it:

1. Use inheritance

class EitherConstructor<L, R>
  implements AsyncMonad<R>, Alternative<R>, Container<R> { ... }
  
class Right<L, R> extends EitherConstructor<L, R> { 
  get [Symbol.toStringTag](): "Right" { ... }
  get name(): "Either" { ... }
  get type(): EitherType.Right { ... }
  getRight(): R { ... }
  getLeft(): undefined { ... } 
  
  private constructor(public readonly right: R) {
    super();
    Object.freeze(this);
  }
}

class Left<L, R> extends EitherConstructor<L, R> {
   ...
   get type(): EitherType.Left { ... }
}
  1. It's simpler. Types can now be defined as:

    type Either<L, R> = Left<L, R> | Right<L, R>
    type Maybe<T> = Just<T> | None<T>; // <T> may be obsolete, cause of T = never by default
    
  2. It's more efficient. Now left and right are different on class level, so difference is expressed through prototype without specific property, so overhead is minimal

2. Make None a singleton

Continuing with classes, it is possible to create one and only one None for all program

class None<T = unknown> extends MaybeConstructor<T> implements SerializedNone {
  static readonly instance = new None<never>();
  static create<T>(): None<T> {
    return None.instance;
  }

  get [Symbol.toStringTag]() {
    return "None";
  }  
}

3. Create rust-like iterator helpers

export function* iterator<T>(
  callback: () => Maybe<T>
): Generator<T, void, void> {
  let result: Maybe<T>;

  while ((result = callback()).isJust()) {
    yield result.unwrap();
  }
}

export async function* asyncIterator<T>(
  callback: () => MaybePromiseLike<Maybe<MaybePromiseLike<T>>>
): AsyncGenerator<T, void, void> {
  let result: Maybe<MaybePromiseLike<T>>;

  while ((result = await callback()).isJust()) {
    yield await result.unwrap();
  }
}

export function* filterMap<T, X>(
  iterable: Iterable<T>,
  filterMap: (value: T, index: number) => Maybe<X>
): Generator<X, void, void> {
  let index = 0;
  for (const value of iterable) {
    const processed = filterMap(value, index++);

    if (processed.isJust()) {
      yield processed.unwrap();
    }
  }
}

4. Add await method for async monads

export interface AsyncMonad<A> extends Monad<A> {
  await<A>(this: AsyncMonad<MaybePromiseLike<A>>): Promise<AsyncMonad<A>>;
}

5. Add zip method

Can be used to refactor .apply()

class EitherConstructor {
  zip<A, B>(either: Either<A, B>): Either<L | A, [R, B]> {
    return this.chain((value) => either.map((right) => [value, right]));
  }
}

AlexXanderGrib avatar Aug 04 '23 00:08 AlexXanderGrib

Hi @AlexXanderGrib. Thank you for the proposal, it looks great. I want to know a little bit more about the use cases of each bullet because it's hard to understand, why people should have an iterator for Maybe or Either. Could you describe a few use cases inside each paragraph?

JSMonk avatar Aug 21 '23 05:08 JSMonk

Hello @JSMonk, haven't used @sweet-monads/iterator, all proposed iterator features implemented there

AlexXanderGrib avatar Aug 25 '23 05:08 AlexXanderGrib