stateless-maybe-js v2.2.3
stateless-maybe-js
This code was ported to fpc as an es6 module. Check here fpc's Maybe docs.
Portable, lightweight, zero-dependency implementation of maybe monad — or option type — in vanilla JavaScript.
Why
There are a bunch of maybe-js libraries, all very similar to each other, so here's why I wrote this one.
- Syntax - Maybes are collections. I think a reasonable interface should be consistent with the collection interface of the host programming language.- This choice enforces the least surprise principle: When you have to deal with a - Maybeobject you can use- filter,- mapand- forEachas you would with an array. Think of a- Maybeas an array of at most one element.
- Stateless - I tried to follow functional paradigm as much as possible. - Every - Maybeobject is an instance of- Justor- Nothing, which one is decided during creation. Once an object is created it will never be explicitly modified by the library.- The wrapped value, anyway, is not side effect free: - filter,- mapand- forEachwill apply a function on that value. Functions passed to those methods shouldn't modify the wrapped value (as far as possible) in order to keep everything stateless.
- Portability - This code could work nearly anywhere, natively: - Here's my test environment for IE6. - Here TypeScript type definitions. - Besides you don't need a whole FP framework to simply create a - Maybe, the minified version is lightweight.
- Type consistency - One thing I don't like in other approaches is that sometimes you can't tell the type of an expression at a glance. - Every method here returns either a - Maybeor a non-- Maybevalue, and I think this property helps a lot method chaining.
- Information hiding - When the wrapped value is publicly exposed the user code can still do something like - if (maybe.value == null) { // ... }- nullifying the purpose of the library. - Here, on the other hand, the wrapped value is hidden and user code has to rely on public methods and properties. 
Read more on design.
Installation
$ npm install stateless-maybe-jsFor browser installation all you need is to include the script:
<script type="text/javascript" src="path/to/dist/maybe.min.js"></script>or require in node:
const maybe = require('stateless-maybe-js');Build
A Makefile will call yarn for you and then uglify-js to produce ./dist/maybe.min.js. Just point your console to the project path and run make.
How to create new Maybe
maybe(someValue) creates a new Maybe object wrapping someValue.
If someValue is null or undefined the result will be a Nothing instance, otherwise it'll be Just(someValue).
var m1 = maybe('hello, world');
var m2 = maybe(undefined);
var m3 = maybe(null);
m1.empty; // false
m2.empty; // true
m3.empty; // trueMaybe objects aren't nested by constructor function.
var m = maybe('hello, world');
// when maybe() receives a maybe monad
// it simply returns the maybe itself
m === maybe(m); // trueIf the emptiness definition isn't trivial (i.e. null or undefined), you can use maybe.nothing and maybe.just().
function maybeYoungPeople (people, maxAge, atLeast) {
  var areYoung = people
    .reduce((acc, p) => acc && p.age <= maxAge, true);
  if (people.length >= atLeast && areYoung) {
    return maybe.just(people);
  } else {
    return maybe.nothing;
  }
}
var people = [ { age: 10 }, { age: 15 } ];
maybeYoungPeople(people, 16, 2).empty; // false
maybeYoungPeople(people, 14, 2).empty; // true
maybeYoungPeople(people, 16, 3).empty; // trueNote that maybe.just(), unlike maybe(), doesn't make any check. A Just instance is always created.
// `Maybe`s *can* contain null or undefined
var m1 = maybe.just(null);
m1.empty; // false
m1.get(); // null
var m2 = maybe('hello, world');
// `Maybe`s *can* be nested with `maybe.just()`
m2 !== maybe.just(m2);
m2 === maybe.just(m2).get();In a nutshell with maybe.just() you are explicitly asking for a Just instance.
If you want to be sure the wrapped value isn't null or undefined, avoid maybe.just().
var m = notSure === 0
  ? maybe.nothing
  : maybe(notSure);Check if a value is a Maybe
maybe.isInstance(null); // false
maybe.isInstance(maybe(null)); // trueType specific constructors
All type-specific constructors also unbox their value before making checks, so 0 and Object(0) are treated identically.
maybe.string(value)
Checks if typeof value is string and it's not an empty string.
maybe.string(1) === maybe.nothing;
maybe.string('') === maybe.nothing;
maybe.string(Object('hello')).get() === 'hello';maybe.number(value)
Returns maybe.just(value) if typeof value === 'number' and value isn't NaN.
// strings are *not* numbers
maybe.number('1') === maybe.nothing;
maybe.number(0/0) === maybe.nothing;
maybe.number(NaN) === maybe.nothing;
maybe.number(Object(1)).get() === 1;maybe.object(value)
Checks if typeof value is object and it's not null.
maybe.object('') === maybe.nothing;
maybe.object(null) === maybe.nothing;
maybe.object(Object('hello')) === maybe.nothing;
maybe.object(Object(1)) === maybe.nothing;Using Maybes
function maybeGetUser (id) {
  // ...
  return maybe(user);
}
// get user's date of birth or 'unknown'
// if user doesn't exist or user.dateOfBirth
// doesn't exist, is null or undefined
maybeGetUser(id)
  .map(user => user.dateOfBirth)
  .getOrElse('unknown');You can use the maybe() function to wrap a lot of useful objects.
function maybeGetElementById (id) {
  return maybe(document.getElementById(id));
}
// remove an element if exist
maybeGetElementById('some-id')
  .forEach(element => element.remove());
// get header's height or 0
maybeGetElementById('header-id')
  .map(header => header.offsetHeight)
  .getOrElse(0);
// execute a function if an element exist
// or another function if it doesn't
maybeGetElementById('some-other-id')
  .forEach(e => console.log('element found!'))
  .orElse(() => console.log('element not found'));
// maybe.toString() returns an empty string
// on nothing
maybeGetElementById('some-node')
  .map(e => e.innerText)
  .toString();Dealing with many Maybes seems hard at first and nesting functions might seem the only way to go. In this case filter could be a good option.
For example we could write a function to update meta description only if the meta tag exists and the given description is a non-empty string:
// plain javascript
function updateMetaDescription (desc) {
  var metaDescription = document.getElementById('meta-description');
  if (metaDescription !== null && typeof desc === 'string' && desc !== '') {
    metaDescription.setAttribute('content', desc);
  }
}
// now nesting maybe.forEach
function updateMetaDescription (desc) {
  maybe(document.getElementById('meta-description'))
    .forEach(function (element) {
      maybe.string(desc).forEach(function () {
        // okay, this is worse
        element.setAttribute('content', desc);
      });
    });
}
// using maybe.filter
function updateMetaDescription (desc) {
  maybe(document.getElementById('meta-description'))
    .filter(() => maybe.string(desc).nonEmpty)
    .forEach(el => el.setAttribute('content', desc));
}Other usage examples
Properties
maybe.empty
true if the maybe is nothing, false otherwise.
maybe.nonEmpty
Negation of maybe.empty.
Methods
maybe.filter(Function fn)
If the maybe is non-empty and fn(value) == false, returns nothing.
Returns the maybe itself otherwise.
maybe.map(Function fn)
If the maybe is non-empty returns maybe(fn(value)).
Returns nothing otherwise.
maybe.forEach(Function fn)
Applies the given function to the value if non-empty, does nothing otherwise. Always returns maybe itself.
maybe.get()
Returns wrapped value or throws an Error if the maybe is empty.
maybe.getOrElse(mixed orElse)
If the maybe is non-empty returns its value, returns orElse otherwise.
orElse can be:
- a function - which is called and its result returned if the maybe is empty.
- any other value - which is returned in case the maybe is empty.
maybe.getOrThrow(throwable e)
Returns the value of the maybe or throws e if the maybe is empty.
maybe.orElse(mixed orElse)
Acts like getOrElse, but returns an maybe instead of its value.
maybe.toString()
Returns the value casted to string if the maybe is non-empty, an empty string otherwise.
TypeScript
Since 2.1.0 TypeScript is also supported:
import * as maybe from 'stateless-maybe-js';
// Optionally import `Maybe` interface for type annotations
import { Maybe } from 'stateless-maybe-js';
// Instead of `maybe()` we can use `maybe.from()`.
let maybeStr: Maybe<string> = maybe.from('1');
// `filter` method won't change the type, so this assignment is valid
maybeStr = maybeStr.filter(str => str.trim() !== '');
function parseNum(str: string): Maybe<number> {
    const parsed = parseFloat(str);
    return isNaN(parsed) ? maybe.nothing : maybe.just(parsed);
}
let maybeNum: Maybe<number> = maybeStr.map(parseNum).forEach(console.log);6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago