Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible library improuvements #67

Open
AlexXanderGrib opened this issue Aug 4, 2023 · 2 comments
Open

Possible library improuvements #67

AlexXanderGrib opened this issue Aug 4, 2023 · 2 comments

Comments

@AlexXanderGrib
Copy link
Contributor

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
Copy link
Owner

JSMonk commented Aug 21, 2023

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
Copy link
Contributor Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants