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
Maybe
s 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
Maybe
object you can usefilter
,map
andforEach
as you would with an array. Think of aMaybe
as an array of at most one element. -
Stateless
I tried to follow functional paradigm as much as possible.
Every
Maybe
object is an instance ofJust
orNothing
, 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
,map
andforEach
will 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
Maybe
or a non-Maybe
value, 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 maybevalue == 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-js
For browser installation all you need is to include the script:
or require in node:
const maybe = ;
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 = ;var m2 = ;var m3 = ; m1empty; // falsem2empty; // truem3empty; // true
Maybe
objects aren't nested by constructor function.
var m = ; // when maybe() receives a maybe monad// it simply returns the maybe itselfm === ; // true
If the emptiness definition isn't trivial (i.e. null
or undefined
), you can use maybe.nothing
and maybe.just()
.
{ var areYoung = people ; if peoplelength >= atLeast && areYoung return maybe; else return maybenothing; } var people = age: 10 age: 15 ; empty; // falseempty; // trueempty; // true
Note that maybe.just()
, unlike maybe()
, doesn't make any check. A Just
instance is always created.
// `Maybe`s *can* contain null or undefinedvar m1 = maybe; m1empty; // falsem1; // null var m2 = ; // `Maybe`s *can* be nested with `maybe.just()`m2 !== maybe;m2 === maybe;
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 ? maybenothing : ;
Check if a value is a Maybe
maybe; // falsemaybe; // true
Type 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 === maybenothing;maybe === maybenothing; maybe === 'hello';
maybe.number(value)
Returns maybe.just(value)
if typeof value === 'number'
and value
isn't NaN
.
// strings are *not* numbersmaybe === maybenothing;maybe === maybenothing;maybe === maybenothing; maybe === 1;
maybe.object(value)
Checks if typeof value
is object
and it's not null
.
maybeobject'' === maybenothing;maybeobjectnull === maybenothing; maybeobjectObject'hello' === maybenothing;maybeobjectObject1 === maybenothing;
Using Maybes
{ // ... return ;} // get user's date of birth or 'unknown'// if user doesn't exist or user.dateOfBirth// doesn't exist, is null or undefined ;
You can use the maybe()
function to wrap a lot of useful objects.
{ return ;} // remove an element if exist ; // get header's height or 0 ; // execute a function if an element exist// or another function if it doesn't ; // maybe.toString() returns an empty string// on nothing ;
Dealing with many Maybe
s 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 { var metaDescription = document; if metaDescription !== null && typeof desc === 'string' && desc !== '' metaDescription; } // now nesting maybe.forEach { ;} // using maybe.filter { ;}
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:
; // Optionally import `Maybe` interface for type annotations; // Instead of `maybe()` we can use `maybe.from()`.; // `filter` method won't change the type, so this assignment is validmaybeStr = maybeStr.filterstr.trim !== ''; ;