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

monadsjs

v0.0.3

Published

Ready to use monads in examples

Downloads

8

Readme

monadsjs

Build Status

Installation

npm install --save monadsjs

Monads

Don't forget to check tests.

Identity

It's the most basic monad implementation which only wraps a value. It doesn't make much sense to use this monad as is. Understanding Identity monad is quite crucial to move forward. It can be a starting point for any new monad.

// Identity.unit :: a -> M a
// Identity.bind :: (a -> M b) -> M b
// Identity.map  :: (a -> b) -> M b

Identity.unit("Monads are awesome!")
  .bind(str => Identity.unit(str.split(" ")))
  .map(words => words.length);

// Identity(3)

Maybe (Just | Nothing)

Maybe monad represents a value (Just) or monadic zero (Nothing). It's very convinient way to literally, let your code fail. Instead of writing multiple checks and try-catches just check type of the value at the end of transformations chain. In this particular implementation Maybe is just a union type which exists only if you are using TypeScript.

Just wraps actual value.

// Just.unit :: a -> Just a
// Just.bind :: (a -> Just b | Nothing) -> Just b | Nothing
// Just.map  :: (a -> b) -> Just b | Nothing

Just.unit("Monads are awesome!")
  .bind(str => Just.unit(str.split(" ")))
  .map((words: string[]) => words.length);

  // Just(3)

At that point it's really similar to Identity monad. Things looks different when Nothing comes in. Nothing can be explicity returned from function passed to bind or when error occures. Further execution is stopped. In the following example double is not going to be called due to TypeError throwed in firstWordLength when empty array is passed.

// Nothing.unit :: () -> Nothing
// Nothing.bind :: (a -> Just b | Nothing) -> Nothing
// Nothing.map  :: (a -> b) -> Nothing

const firstWordLength = words => words[0].length;
const double = n => n * 2;

Just.unit([])
  .bind(strs => Just.unit(firstWordLength(strs)));
  .map(double);

  // Nothing()

Either (Left | Right)

Either monad is almost like a Maybe monad. Right and Just are basically the same. Either unlike Maybe doesn't have monadic zero. Left is supposed to wrap Error. Left can be explicity returned from function passed to bind or when error occures. In this particular implementation Either is just a union type which exists only if you are using TypeScript. In the following example double is not going to be called due to TypeError throwed in firstWordLength when empty array is passed.

// Right.unit :: a -> Right a
// Right.bind :: (a -> Right b | Left b) -> Right b | Left b
// Right.map  :: (a -> b) -> Right b | Left b

Right.unit("Monads are awesome!")
  .bind(str => Right.unit(str.split(" ")))
  .map((words: string[]) => words.length);

  // Right(3)
// Left.unit :: a -> Left a
// Left.bind :: (a -> Right b | Left b) -> Left a
// Left.map  :: (a -> b) -> Left a

const firstWordLength = words => words[0].length;
const double = n => n * 2;

Right.unit([])
  .bind(strs => Right.unit(firstWordLength(strs)))
  .map(double);

  // Left(TypeError: Cannot read property 'length' of undefined)

IO

IO monad was invented to make it possible to perform side effects in pure functional languages (here I have Haskell in mind). In JavaScript we can take an advantage of IO monads to change unpure functions into pure which makes them easy to test. This implementation is also a Setoid. IO.equals makes it easy to deeply compare two IO monads.

// IO.unit :: (a, ...) -> IO a, [...]
// IO.bind :: ((a, ...) -> IO) -> IO a, [...]
// IO.run  :: () -> void

function sayHello(name) {
  return IO.unit(alert, `Hello ${name}!`);
}

const m = sayHello("World"); // IO(sayHello, ["World"])

assert(m.equals(IO.unit(alert, "Hello World!"))); // passes

m.run(); // alerts: Hello World!

Continuation (Promise)

Promise out of the box is a great Continuation monad implementation! Promise.resolve corresponds to unit and Promise.then corresponds to bind. Provided Continuation implementation is a minimal wrapper for Promise with methods you know from other monads (bind and unit). Don't use it, use Promise.

// Continuation.unit :: a -> Continuation a
// Continuation.bind :: (a -> b) -> Continuation b

Continuation.unit(fetch("https://api.github.com/users/octocat"))
  .bind(response => response.json());

  // Continuation(<Promise>{ "login": "octocat", "id": ... })

List

List monad is a clever one. List constructor accepts Iterable and allows for lazy transformations. It is achieved using generators. What is more, List implements Symbol.iterator so you can iterate through it like any regular iterator.

// List.unit    :: a -> List a
// List.bind    :: (a -> List b) -> List b
// List.map     :: (a -> b) -> List b
// List.forEach :: (a -> void) -> void

const lazySpy = sinon.spy();
const nextLazySpy = sinon.spy();

const m = List.of([1, 2, 3])
  .map(x => {
    lazySpy();
    return x + 1;
  }).map(x => {
    nextLazySpy();
    return x + 1;
  });

  // List([object Generator])

const asIterable = m[Symbol.iterator]();

// no computation was done so far

assert(lazySpy.notCalled);
assert(nextLazySpy.notCalled);

assert(asIterable.next().value === 3);

// only first value was computed

assert(lazySpy.calledOnce);
assert(nextLazySpy.calledOnce);

assert(asIterable.next().value === 4);
assert(lazySpy.calledTwice);
assert(nextLazySpy.calledTwice);

assert(asIterable.next().value === 5);
assert(lazySpy.calledThrice);
assert(nextLazySpy.calledThrice);

Monads next

Monad next is a bit different approach to implementing monads in JavaScript. It requires :: bind operator wich is not yet supported in TypeScript (shame on you TypeScript, ave Babel!) and having types when implemening monads is more important for me at this very moment.

const List = {
  *unit(iterable) {
    yield* iterable;
  },

  *bind(fn) {
    for (let x of this) {
      yield fn(x);
    }
  }
};

const m = List.unit([1, 2, 3])::List.bind(x => x + 1)::List.bind(x => x + 1);

Check it live.