kanskje
TypeScript icon, indicating that this package has built-in type declarations

2.0.2 • Public • Published

kanskje

Simple Maybe monad written in TypeScript

npm module Coverage status Build status dependencies status devDependencies status Code style: Prettier

Set your code free

Table of Contents

Introduction

A Maybe represents a wrapper around any value - most commonly values that might be nullable. Having this wrapper is useful because it guards against null and undefined when doing computations on the value. The idea is that the Maybe deals with nullables internally, thus allowing one to write simpler code with fewer conditionals. Not until the very end when it's needed to unwrap the value again, is it necessary to deal with the fact that it was or somehow became nullable.

Maybe.fromNullable(getValueThatMightNotBeThere())
  .map(doSomething)
  .map(doAnotherThing)
  .map(doFinalThing)
  .getOrElse(defaultValue);

Even though they can't be constructed individually, the Maybe consists of two classes: Just and Nothing. The Maybe is a Just if it holds a value and a Nothing if it doesn't.

const toUpper = a => a.toUpperCase();
 
Maybe.fromNullable(['foo', 'bar', 'baz'][2]) // Just('baz')
  .map(toUpper) // Just('BAZ')
  .getOrElse('No value here');
// => 'BAZ'
 
Maybe.fromNullable(['foo', 'bar', 'baz'][3]) // Nothing
  .map(toUpper) // Nothing
  .getOrElse('No value here');
// => 'No value here'

There exists a number of great resources on Maybe monads - including The Marvellously Mysterious JavaScript Maybe Monad by @jrsinclair and Professor Frisby's Mostly Adequate Guide to Function Programming by @drboolean - that one might want to get familiar with. If you are used to Promises, you are essentially already familiar with monads.

Why kanskje?

kanskje stems from the need of a simple Maybe monad with type declarations making it usable for both JavaScript and TypeScript. The fact that the Maybe is written in TypeScript makes sure that it only contains methods that are actually possible to express using the TypeScript type system.

The source code is simple with one-line functions and no aliases, and yet the function declarations - using Generic Types of TypeScript - should be self-documenting.

Unlike some Maybe monads, kanskje doesn't perform behind the scenes conversions from Just to Nothing. As an example you can pass any mapper function, f: (a: A) => B, to map and be sure that the return type of f isn't checked. A Just is kept a Just even if f returns a nullable:

const unsafeProp = b => a => a[b];
 
Maybe.of({ name: 'Alice' }) // Just({ name: 'Alice' })
  .map(unsafeProp('age')) // Just(undefined)
  .getOrElse(25);
// => undefined

If Just(undefined) is not the desired outcome, the mapper function, f, needs to return a Maybe and be passed to chain instead:

const safeProp = b => a => Maybe.fromNullable(a[b]);
 
Maybe.of({ name: 'Alice' }) // Just({ name: 'Alice' })
  .chain(safeProp('age')) // Nothing
  .getOrElse(25);
// => 25

Usage

This is a CommonJS module.

Import the exported functions as named imports:

const { all, empty, fromNullable, of } = require('kanskje');

Or namespace them all under e.g. Maybe:

const Maybe = require('kanskje');

Or use ES2015 module syntax if your environment supports that:

import { all, empty, fromNullable, of } from 'kanskje';
 
import * as Maybe from 'kanskje';

API

Constructing Maybes

To construct a Maybe one of the following functions can be used:

all

Accepts an array or a tuple of Maybes and returns a single Maybe. If all the Maybes were Justs, the resulting Maybe will be a Just containing their values. If any of the Maybes where a Nothing, the resulting Maybe will be a Nothing.

  • Signature:

    all<A>(maybesMaybe<A>[])Maybe<A[]>
     
    all<A, B>(maybes[Maybe<A>, Maybe<B>])Maybe<[A, B]>
     
    all<A, B, C>(maybes[Maybe<A>, Maybe<B>, Maybe<C>])Maybe<[A, B, C]>
     
    all<A, B, C, D>(
      maybes[Maybe<A>, Maybe<B>, Maybe<C>, Maybe<D>]
    )Maybe<[A, B, C, D]>
     
    all<A, B, C, D, E>(
      maybes[Maybe<A>, Maybe<B>, Maybe<C>, Maybe<D>, Maybe<E>]
    )Maybe<[A, B, C, D, E]>
  • Example:

    Maybe.all([Maybe.of('foo'), Maybe.of('bar'), Maybe.of('baz')]);
    // => Just(['foo', 'bar', 'baz'])
    Maybe.all([Maybe.of('foo'[1]), Maybe.of('bar'[3]), Maybe.of('baz'[2])]);
    // => Just(['o', undefined, 'z'])
    Maybe.all([
      Maybe.of('foo'[1]),
      Maybe.fromNullable('bar'[3]),
      Maybe.of('baz'[2]),
    ]);
    // => Nothing

empty

Returns a Nothing.

  • Signature:

    empty<A>()Maybe<A>
  • Example:

    Maybe.empty();
    // => Nothing

fromNullable

Lifts a value into a Maybe but checks if the value is either null or undefined. If that is a case, a Nothing is returned. Otherwise a Just is returned.

  • Signature:

    fromNullable<A>(aNullable<A>)Maybe<A>
  • Example:

    Maybe.fromNullable('foo');
    // => Just('foo')
    Maybe.fromNullable(['foo', 'bar', 'baz'][3]);
    // => Nothing

of

Lifts a value into a Maybe, more specifically: a Just.

  • Signature:

    of<A>(a: A): Maybe<A>
  • Example:

    Maybe.of('foo');
    // => Just('foo')
    Maybe.of(['foo', 'bar', 'baz'][3]);
    // => Just(undefined)

Maybe methods

Once a Maybe is constructed, the following methods are accessible on the instance:

chain

Accepts a mapper function, f, that returns a Maybe and automatically unwraps the outer Maybe to not end up with a Maybe<Maybe<B>>.

  • Signature:

    chain<B>(f: (a: A) => Maybe<B>)Maybe<B>
  • Example:

    const safeHead = xs => Maybe.fromNullable(xs[0]);
     
    Maybe.of([1, 2, 3]).chain(safeHead);
    // => Just(1)
     
    Maybe.of([]).chain(safeHead);
    // => Nothing

filter

Accepts a predicate function, f, and converts the Maybe from a Just to a Nothing if its value does not adhere. If the Maybe is already a Nothing it remains a Nothing.

Note: If used with TypeScript f can be a Type Guard.

  • Signature:

    filter(f: (a: A) => boolean)Maybe<A>
    filter<B extends A>(f: (a: A) => a is B)Maybe<B>
  • Examples:

    Using a predicate function:

    const isEven = a => a % 2 === 0;
     
    Maybe.of(4).filter(isEven);
    // => Just(4)
     
    Maybe.of(7).filter(isEven);
    // => Nothing

    Using a TypeScript Type Guard:

    interface Admin extends Person {
      password: string;
    }
     
    interface Person {
      name: string;
    }
     
    function isAdmin(a: Person): a is Admin {
      return a.hasOwnProperty('password');
    }
     
    const carl: Admin = {
      name: 'Carl',
      password: '1234',
    };
     
    const persons: Person[] = [
      {
        name: 'Alice',
      },
      carl,
    ];
     
    Maybe.fromNullable(persons[0]).filter(isAdmin);
    // => Nothing
     
    Maybe.fromNullable(persons[1]).filter(isAdmin);
    // => Just({ name: 'Carl', password: '1234' })

fold

Accepts two functions: a mapper function, f, and a function with the same return type, g. If the Maybe is a Just its value is mapped and returned using f. If it's a Nothing the result of g is returned.

  • Signature:

    fold<B>(f: (a: A) => B, g: () => B)B
  • Example:

    const unsafeProp = b => a => a[b];
     
    const persons = [
      {
        name: 'Alice',
      },
      {
        name: 'Bob',
      },
    ];
     
    Maybe.fromNullable(persons[1]).fold(
      unsafeProp('name'),
      () => 'A person does not exist',
    );
    // => 'Bob'
     
    Maybe.fromNullable(persons[2]).fold(
      unsafeProp('name'),
      () => 'A person does not exist',
    );
    // => 'A person does not exist'

getOrElse

Accepts a default value, a, and returns that if the Maybe is a Nothing. Otherwise the value of the Just is returned.

  • Signature:

    getOrElse(aA)A
  • Example:

    Maybe.fromNullable(getPortFromProcess()).getOrElse(3000);

isJust

Returns true if the Maybe is a Just and false if it's a Nothing.

  • Signature:

    isJust()boolean

isNothing

Returns true if the Maybe is a Nothing and false if it's a Just.

  • Signature:

    isNothing()boolean

map

Accepts any mapper function, f, and applies it to the value of the Maybe. If f returns a Maybe, the result will be a nested Maybe.

  • Signature:

    map<B>(f: (a: A) => B)Maybe<B>
  • Example:

    const length = a => a.length;
     
    Maybe.of('foo').map(length);
    // => Just(3)
    const safeHead = xs => Maybe.fromNullable(xs[0]);
     
    Maybe.of([1, 2, 3]).map(safeHead);
    // => Just(Just(1))
     
    Maybe.of([]).map(safeHead);
    // => Just(Nothing)

orElse

Accepts a Maybe, a. If the instance Maybe is a Nothing it is replaced with a. Otherwise it is left unchanged.

  • Signature:

    orElse(aMaybe<A>)Maybe<A>
  • Example:

    const persons = [
      {
        name: 'Alice',
      },
      {
        name: 'Bob',
      },
    ];
     
    Maybe.fromNullable(persons[1]).orElse(Maybe.of({ name: 'Carl' }));
    // => Just('Bob')
     
    Maybe.fromNullable(persons[2]).orElse(Maybe.of({ name: 'Carl' }));
    // => Just('Carl')

unsafeGet

Unsafely unwraps the value from the Maybe. Since Nothings don't contain a value, the function will throw an error if the Maybe happened to be a Nothing.

  • Signature:

    unsafeGet()A | void
  • Example:

    Maybe.fromNullable(5).unsafeGet();
    // => 5
     
    Maybe.of(undefined).unsafeGet();
    // => undefined
     
    Maybe.fromNullable(undefined).unsafeGet();
    // => TypeError: A Nothing holds no value.

Readme

Keywords

none

Package Sidebar

Install

npm i kanskje

Weekly Downloads

1

Version

2.0.2

License

MIT

Unpacked Size

18.6 kB

Total Files

8

Last publish

Collaborators

  • christianhg