npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

for-comprehension-ts

v2.0.0

Published

Something like Scala's for comprehension in Typescript

Downloads

10

Readme

for-comprehension-ts

Programming with monads such as Result, Either or Try aka Railway Oriented Programming is an elegant way to write programs that clearly separate domain logic from error handling. With for-comprehension-ts we aim to overcome three major limitations that we found in the application of this approach to real-world programs with TypeScript:

1. Improved code readability

Typical applications such as validation of complex data objects. Such scenarios may have 10 or more steps and the output of the first steps is required in multiple further steps. Implementing this with method chaining am.flatMap((a) => bm.flatMap((b) => ...) quickly leads to large closures and according accordingly large pyramids of braces. We also think that pipes pipe(result, (a) => success([a, a + 1]), ([a, b]) => ...) are no elegant solution to this problem as parameters must be explicitly passed along functions.

In other programming languages there are language features such as Haskell's do-notation or Scala's for-comprehension that provide a solution to this problem. For example, Scala's for-comprehension looks like this:

val result: Either[String, Int] =
  for {
    dividend <- Right(42)
    divisor <- Right(2)
    divisorVerified <- if (divisor > 0) Right(divisor)
                       else Left("Divisor must be > 0!")
  } yield dividend / divisor

println(result match {
  case Right(value) => value
  case Left(error) => error
})

With for-comprehension-ts we provide a similar syntax for TypeScript:

const result = For
    .result<string>()
    .sync("dividend", () => success(42))
    .fm("divisor", () => success(2))
    .fm("divisorVerified", ({divisor}) =>
        divisor > 0
            ? success(divisor)
            : failure<number, string>("Divisor must be > 0!"))
    .yield(({dividend, divisorVerified}) => dividend / divisorVerified)

console.log(isSuccess(result) ? result.value : result.error)

We think that for-comprehension-ts improves readability of railway oriented code. We start with For.result<E>() to set a custom error type E. With each of the following lines (constructor sync or flatMap fm) we add a value to a parameter object that is available to all following steps. The field name is set by passing a string as first parameter. By destructuring its easy to see where these values are read later on. Finally, by passing a map function to the yield operator, a single value is derived from all intermediate ones. Of course, the programm will only be executed until the first step results into failure. Besides this, our programm declaration is fully type-safe and requires only few type hints.

In the first version of this library, we tried to make for-comprehension generic to support custom monads. However, due to the missing support for higher kinded types we failed to do this in a way that is both easy to read and without the need for excessive type hints. Thus, we focussed on a Result<T, E> monad as , from our experience, it is most important in real-word applications.

2. No unboxing of promises

Another major problem we were facing while working with Monads in TypeScript was the permanent dealing with Promise<Result<T, E>>. With for-comprehension-ts, we provide a seamless integration of regular and async functions using the same syntax:

const result = await For
    .result<string>()
    .async("dividend", async () => success(42))
    .fm("divisor", () => success(2))
    .fm("divisorVerified", ({divisor}) =>
        divisor > 0
            ? success(divisor)
            : failure<number, string>("Divisor must be > 0!"))
    .yield(({dividend, divisorVerified}) => dividend / divisorVerified)

console.log(isSuccess(result) ? result.value : result.error)

The only differences are:

  • alternative constructor async<E>
  • support for async functions for both constructor async and flatMap fm
  • yield<T> will result into a Promise<Result<T, E>>.

3. Optimization of async programs

There are scenarios where the simultaneous execution of multiple promises will lead to faster execution of programs, for example, if waiting times for external services do not add up. To allow for such optimizations we provide fmp, a variant of flatMap where required input fields of the parameter object must be whitelisted. Such programs form a directed acyclic graphs (DAG) whose vertices are named values (e.g., a = 3) and where edges are asynchronous flatMap operations. Based on this information, we can optimize promise execution. However, although it is possible to declare such programs, the actual optimization is not implemented yet. We hope to add this feature soon.

const result = await For
    .result<string>()
    .async("a", () => serviceA())
    .fmp("b", ["a"], ({a}) => serviceB(a))
    .fmp("c", ["a"], ({a}) => serviceC(a))
    .yield(({b, c}) => b + c)

Contribution

If you like for-comprehension-ts, please feel free to contribute in any way!