JSMonk / sweet-monads

The library which provides useful monads, interfaces, and lazy iterators.
MIT License
343 stars 22 forks source link

Possible library improuvements #67

Open AlexXanderGrib opened 1 year ago

AlexXanderGrib commented 1 year ago

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]));
  }
}
JSMonk commented 1 year ago

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?

AlexXanderGrib commented 1 year ago

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