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

maybeasy

v6.3.0

Published

Maybe implemented in TypeScript

Downloads

6,215

Readme

maybeasy

Build Status semantic-release

Whether we like it or not, not every computation in a program is going to return a value. Sometimes there's no value to return. Other times, something goes wrong and we're just not sure what value to return.

In JavaScript we often will return null or undefined to represent the value of Nothing, however we arrive at that value. This can often lead to failures at runtime when we forget (or are unaware) that a value may be nothing. Strict null checking makes this situation tolerable in Typescript, if only by nagging you every place you need to check for Nothing.

Arrays are less prone to these types of failures, because if when there is nothing, we return an empty array. We can safely apply functions to an array without being concerned if there are values present, because an empty array and a populated array have the same interface. Can that same principle be applied to singular values?

Sure it can! The Maybe type offers a way to express that a value may be something, or it may be nothing. Our intuition for mapping over arrays is applicable here.

the functor (this map)

Given a computation that may or may not return a value, we can apply logic to this value by mapping pure functions over the result of the computation. For example:

const fetchSomething = (): Maybe<number> => ...; // <-- may or may not return something

const add2 = (n) => n + 2;

fetchSomething().map(add2); //

In this example, we add2 to the number that we fetched. But if the there is no number -- it's Nothing -- the result of add2 is... Nothing. We won't get a runtime error.

chaining (the flat map)

Let's say that we have two computations. Both may return nothing, but one of the computations depends on the other.

const fetchSomething = (): Maybe<number> => ...;

const fetchSomethingElse = (n: number): Maybe<string> => ...; // <-- also may or may not return something

We could use a map here, as in this examples:

fetchSomething().map(fetchSomethingElse);

The problem with this is that we will end up a maybe nested inside another maybe. You intuition for Arrays applies here, too; if we map over an Array with a function that returns an Array, we end up with an Array of Arrays. That same thinking applies here; we'll end up with Maybe<Maybe<string>>.

To chain computations that both may return nothing, we need a different tool: andThen. We can rewrite our previous example, but just replace map with andThen:

fetchSomething().andThen(fetchSomethingElse);

If either computation is Nothing, the result is nothing. If both computations succeed, then we have Maybe<string>.

building an object

A common pattern in javascript is build an object from a set of computations. When those computations may or may not return a value, it can be useful to chain them together using Maybe.

Given our functions, you can chain them together using andThen. It looks like this:

fetchSomething().andThen(a => fetchSomethingElse.andThen(b => just({ a, b })));

If the object is fairly complex, this nesting can be quite deep.

fetchSomething()
.andThen(a =>
  fetchSomethingElse().andThen(b =>
    fetchC().andThen(c =>
      fetchD().andThen(d =>
        fetchE().andThen(e =>
          ({ a, b, c, d, e })
        )
       )
      )
    );

This is barely distinguishable from callback hell. The assign method helps flatten this out by allowing us to build an object incrementally. Here's the last code example using assign:

just({})
  .assign('a', fetchSomething())
  .assign('b', fetchSomethingElse())
  .assign('c', fetchC())
  .assign('d', fetchD())
  .assign('e', fetchE());

assign also accepts a function that returns a Maybe value as the second argument. Use this when you need to calculate one value of the object, based on a previously calculated value. For example:

just({})
  .assign('a', just(8))
  .assign('b', scope => just(scope.a + 42)); // --> Just { a: 8, b: 50 }

unwrapping the value

At some point, we may need to send our result to another part of the system. The other part of the system may not understand Maybe values. Or possibly this value needs to be serialized as a string for sending to a third party. We need a safe way to unwrap this value. For this purpose we have getOrElse and getOrElseValue.

getOrElse and getOrElseValue will return the value if it is present (a Just), but also requires us to provide a default value, in the case that we have Nothing. getOrElseValue is strict and takes a value of the generic type of the Maybe. getOrElse is lazy. It takes a function that returns a type of the generic type of the Maybe. The function will only be evaluated if the Maybe is Nothing. Prefer usinf getOrElse if the default value is expensive to calculate.

This makes unwrapping the value safe.

putting it all together

We can, of course, chain and map all we want to create a pipeline of data processors. At the end we can unwrap our value for consumption by humans or other systems. For example:

fetchSomething()
  .map(add2)
  .andThen(fetchSomethingElse)
  .getOrElse('No data');

install

npm install --save maybeasy

yarn add maybeasy

usage

import { just, nothing } from 'maybeasy';

function parse(s) {
  try {
    return just(JSON.parse(s));
  } catch (e) {
    return nothing();
  }
}

docs

API