Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

monadsjs

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

monadsjs

Ready to use monads in examples

  • 0.0.3
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
1
Maintainers
1
Weekly downloads
 
Created
Source

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.

Keywords

FAQs

Package last updated on 26 Jan 2017

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc