GitXplorerGitXplorer
b

mandolin

public
14 stars
1 forks
7 issues

Commits

List of commits on branch master.
Unverified
5b321868989e2ad08506d622bf4a1b1e6effbffb

add initial files for a react sample app

bbioball committed 9 years ago
Unverified
5ebd0b5a8ab61aaf5138155c1db81b5f916ca4bb

add tests, add equals function

bbioball committed 9 years ago
Unverified
439953e7a3390ddc6b324b99e2575acb68756ac9

add m.any

bbioball committed 9 years ago
Unverified
819383b82588cc77b4017c4cc7333ab21a4dfdb2

add linting, add build before test in travis.yml

committed 9 years ago
Unverified
88db67c449bac279fc94b06f31123795027e9ad0

build it out

committed 9 years ago
Unverified
4b9d1875a481c380df5108849100eccc1e9ca916

add the return method to each abstract class

committed 9 years ago

README

The README file for this repository.

Mandolin

Build Status

Painlessly enhance your JavaScript with monadic types.

const definition = m.define({
  firstName: m.string,
  lastName: Option.as(m.string),
  email: Option.as(m.string),
  phoneNumber: Either.as(m.string, m.number)
});

definition.parse({
  firstName: "Bob",
  lastName: "McAdoo",
  email: null,
  phoneNumber: "444-444-4444"
})
// Right({ 
//   firstName: "Bob",
//   lastName: Some("McAdoo"),
//   email: None(),
//   phoneNumber: Left("444-444-4444")
// })

What is a monad?

In brief, a Monad is a wrapper around a value that allows you to make safe, composable operations. It eliminates the need to throw errors, as well as the need for things like null values. A JavaScript Array is a monad-like data type, but doesn't fully satisfy the rules of being a monad, given that it doesn't have a bind (or flatMap) function.

A monad is a type that satisfies the following methods:

  • A composition function, called >>=, bind, or flatMap, that has type (A) => M<B>, and returns type M<B>.
  • A return (or unit), that has type (A) => M<A>.

Monads are common in functional programming languages.

Here are some great resources that discuss monads:

In this library, the monadic bind is called flatMap, in order to not conflict with Function.prototype.bind. The monadic return is called unit, to not cause any confusion with the return keyword.

Installation

npm install mandolin
bower install mandolin

Currently, you must use a CommonJS loader to require this module.

const m = require('mandolin');
const { Option, Some, None } = m;

Available monads

Option

An Option is a type comprising of Some and None. A value of type Option can either be a Some, in which it holds a value, or a None, in which it holds no value. This is used in lieu of null and undefined.

Example:

const bobsEmail = new Some("bob@mcadoo.com");
// We have Bob's email
const sandrasEmail = new None();
// we do not have Sandra's email

The way to get a reference to the actual value is through either map, flatMap or match.

bobsEmail.map((email) => doThingWithEmail(email));

This is similar to writing a null check:

if (email !== null || email !== undefined) {
  doThingWithEmail(email)
}

Either

A disjoint union of Left and Right, and is right-biased. map and flatMap are only called if it is a Right. This is similar to an Option, in that Left : None :: Right : Some. The difference is that a Left can also hold values.

Reads

Mandolin uses Reads objects, which are not monads, but whose purpose are to serialize values into algebraic types (Option, Either).

A Reads is created with this signature:

const r = new Reads(reader);

Where reader is a function that accepts a value, and returns an Either; a Right with a successful value, or a Left with an unsuccessful value. For example, a reader of even numbers would look like this:

const reader = (v) => v % 2 ? new Left("number is not even") : new Right(v);

You can think of a Reads as a rule for serializing something. Reads can be freely chained together, to further define a rule.

const greaterThan10 = new Reads((v) => v < 10 ? new Left("number is less than 10") : new Right(v));
const evenAndGreaterThan10 = r.with(greaterThan10);

evenAndGreaterThan10.getValue(4) // Left("number is not event")
evenAndGreaterThan10.getValue(14) // Right(14)

Reads can be chained via with, map and flatMap.

Parser

Along with Reads, Mandolin has Parsers that accept arbitrary objects of key --> Reads pairs, and returns an either with a successful serialized object, or an error.

This provides a generic way to validate objects, and coerce values into algebraic data types.

You may create a definition with this signature:

const definition = m.define({
  age: m.number,
  name: m.string
});

In this example, m.number and m.string are pre-baked Reads for primitives. The following are all available:

  • m.number
  • m.string
  • m.boolean
  • m.array
  • m.object
  • m.undefined
  • m.null
  • m.any

The return value of m.define is a Parser, which is a special type of Reads. Thus, nested objects are simply notated as such:

const definition = m.define({
  age: m.number,
  name: m.string,
  address: m.define({
    street1: m.string
  })
});

Option and Either both have a generic way to create Reads.

const definition = m.define({
  meta: Option.reads,
  email: Option.as(m.string)
});

Option.reads will return either a None(), or a Some(<Any>). Option.as() accepts a Reads argument, to perform further validation after casting away null or undefined.

Once a definition has been instantiated, calling parse will return an Either, with the Right holding the successfully parsed value, and the Left holding the first error encountered while parsing.

definition.parse({
  meta: "foo",
  email: null
})
// returns Right({
//   meta: Some("foo"),
//   email: None()
// });

Types

This library makes no assumptions about type safety. The approach, rather, is to use Reads combinators to serialize values that follow certain sets of rules. For example, in Scala, an Option of a String is notated as such:

Option[String]

The seemingly equivalent example in our library is this:

Option.as(m.string)

The difference is, Option.as(m.String) is not a type, but a rule for reading in values. I can just as easily write another rule for Option.as(evenNumber). Essentially, this is the same thing as types, but I think makes more sense in a world that doesn't perform any compile-time type checking.

Pattern Matching

JavaScript doesn't have pattern matching built into the language. However, each algebraic data type in this library comes with a match function that behaves like pattern matching.

new Left("foo").match({
  Left (str) { ... },
  Right (str) { ... }
});

The return value of match is the return value of whichever function ends up being called. This is analagous to Scala's match.

foo match {
  case Left(_) => ???
  case Right(_) => ???
}

Fantasy-land

This module is fantasy-land compliant. chain is an alias of flatMap, and of is an alias of unit.