├── .gitignore ├── package.json ├── .jshintrc ├── Makefile ├── LICENSE ├── maybe.min.js ├── maybe.js └── README.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maybe-js", 3 | "version": "0.1.0", 4 | "description": "A Maybe monad implementation", 5 | "main": "maybe.js", 6 | "author": "Andrew Stewart (http://andrew.stwrt.ca)", 7 | 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/stewart/maybe.js" 11 | }, 12 | 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "es3": true, 3 | 4 | "globals": { 5 | "define": true, 6 | "module": true 7 | }, 8 | 9 | "boss": true, 10 | "curly": true, 11 | "eqeqeq": true, 12 | "eqnull": true, 13 | "immed": true, 14 | "indent": 2, 15 | "latedef": true, 16 | "maxlen": 80, 17 | "newcap": true, 18 | "noarg": true, 19 | "nonew": true, 20 | "quotmark": "double", 21 | "strict": true, 22 | "sub": true, 23 | "undef": true, 24 | "unused": true 25 | } 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES = maybe.js 2 | MINIFIED = ${SOURCES:.js=.min.js} 3 | VERSION = $(shell node -e "console.log(require('./package.json').version)") 4 | PREAMBLE = "/*! maybe.js v$(VERSION) | (c) 2015 Andrew Stewart | MIT license */" 5 | 6 | %.min.js: %.js 7 | @echo "Minifying $< => $@" 8 | @uglifyjs --compress --mangle --preamble $(PREAMBLE) -- $< 2>/dev/null > $@ 9 | 10 | all: $(MINIFIED) 11 | 12 | lint: 13 | @jshint maybe.js 14 | 15 | release: lint $(MINIFIED) 16 | @git push origin master 17 | @git checkout release ; git merge master ; git push ; git checkout master 18 | @git tag -m "$(VERSION)" v$(VERSION) 19 | @git push --tags 20 | @npm publish ./ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Andrew Stewart 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /maybe.min.js: -------------------------------------------------------------------------------- 1 | /*! maybe.js v0.1.0 | (c) 2015 Andrew Stewart | MIT license */ 2 | !function(n,t){"use strict";"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():n.maybe=t()}(this,function(){"use strict";function n(n){return function(){return!n.apply(this,arguments)}}function t(n){return function(t){return Object.prototype.toString.call(t).slice(8,-1)===n}}function r(t){function i(){return null==t}function o(n){return e(n)&&e(n.toString)?"Maybe"===n.toString().slice(0,5):!1}function l(n){return o(n)?n:r(n)}function c(n){if(i())return r.nothing;var o=Array.prototype.slice.call(arguments,1);return u(n)?e(t[n])?l(t[n].apply(t,o)):r.nothing:(o.unshift(t),l(n.apply(null,o)))}function a(n){return i()?r.nothing:l(t[n])}function f(n){return i()?r.nothing:e(n)?c.apply(null,arguments):u(n)&&e(t[n])?c.apply(null,arguments):a.apply(null,arguments)}function p(n,r){return g()&&n.call(r,t),l(t)}function s(n){return l(g()?t:e(n)?n.call(null):n)}var g=n(i);return f.get=a,f.bind=c,f.tap=p,f.or=s,f.isNothing=i,f.isValue=g,f.toString=function(){return"Maybe("+(i()?"empty":t)+")"},f.toJSON=function(){return{type:"Maybe",value:t}},Object.defineProperty(f,"value",{get:function(){return t}}),f}var e=t("Function"),u=t("String");return r.nothing=r(void 0),r.lift=function(n){return function(){return r(n.apply(this,arguments))}},r}); -------------------------------------------------------------------------------- /maybe.js: -------------------------------------------------------------------------------- 1 | /** 2 | * maybe.js 3 | * 4 | * Copyright (c) 2015 Andrew Stewart 5 | * Released under the MIT license 6 | */ 7 | 8 | (function (root, factory) { 9 | "use strict"; 10 | 11 | if (typeof define === "function" && define.amd) { 12 | // AMD 13 | define([], factory); 14 | } else if (typeof exports === "object") { 15 | // Node 16 | module.exports = factory(); 17 | } else { 18 | // Browser 19 | root.maybe = factory(); 20 | } 21 | }(this, function () { 22 | "use strict"; 23 | 24 | function not(fn) { 25 | return function() { 26 | return !(fn.apply(this, arguments)); 27 | }; 28 | } 29 | 30 | function isA(type) { 31 | return function(thing) { 32 | return Object.prototype.toString.call(thing).slice(8, -1) === type; 33 | }; 34 | } 35 | 36 | var isFunction = isA("Function"), 37 | isString = isA("String"); 38 | 39 | function maybe(value) { 40 | function isEmpty() { return value == null; } 41 | 42 | var notEmpty = not(isEmpty); 43 | 44 | function isWrapped(thing) { 45 | if (isFunction(thing) && isFunction(thing.toString)) { 46 | return thing.toString().slice(0, 5) === "Maybe"; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | function wrap(value) { 53 | if (isWrapped(value)) { return value; } 54 | return maybe(value); 55 | } 56 | 57 | function bind(fn) { 58 | if (isEmpty()) { return maybe.nothing; } 59 | 60 | var args = Array.prototype.slice.call(arguments, 1); 61 | 62 | if (isString(fn)) { 63 | if (isFunction(value[fn])) { 64 | return wrap(value[fn].apply(value, args)); 65 | } 66 | 67 | return maybe.nothing; 68 | } 69 | 70 | args.unshift(value); 71 | 72 | return wrap(fn.apply(null, args)); 73 | } 74 | 75 | function get(key) { 76 | if (isEmpty()) { return maybe.nothing; } 77 | return wrap(value[key]); 78 | } 79 | 80 | function access(opt) { 81 | if (isEmpty()) { return maybe.nothing; } 82 | 83 | if (isFunction(opt)) { 84 | return bind.apply(null, arguments); 85 | } 86 | 87 | if (isString(opt) && isFunction(value[opt])) { 88 | return bind.apply(null, arguments); 89 | } 90 | 91 | return get.apply(null, arguments); 92 | } 93 | 94 | function tap(fn, thisArg) { 95 | if (notEmpty()) { 96 | fn.call(thisArg, value); 97 | } 98 | 99 | return wrap(value); 100 | } 101 | 102 | function or(thing) { 103 | if (notEmpty()) { return wrap(value); } 104 | 105 | if (isFunction(thing)) { 106 | return wrap(thing.call(null)); 107 | } 108 | 109 | return wrap(thing); 110 | } 111 | 112 | access.get = get; 113 | access.bind = bind; 114 | 115 | access.tap = tap; 116 | access.or = or; 117 | 118 | access.isNothing = isEmpty; 119 | access.isValue = notEmpty; 120 | 121 | access.toString = function() { 122 | return "Maybe(" + (isEmpty() ? "empty" : value) + ")"; 123 | }; 124 | 125 | access.toJSON = function() { 126 | return { 127 | type: "Maybe", 128 | value: value 129 | }; 130 | }; 131 | 132 | Object.defineProperty(access, "value", { 133 | get: function() { 134 | return value; 135 | } 136 | }); 137 | 138 | return access; 139 | } 140 | 141 | maybe.nothing = maybe(void 0); 142 | 143 | maybe.lift = function(fn) { 144 | return function() { 145 | return maybe(fn.apply(this, arguments)); 146 | }; 147 | }; 148 | 149 | return maybe; 150 | })); 151 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # maybe.js 2 | 3 | This library contains a JS implementation of the [Maybe monad][]. 4 | 5 | It's a bit more flexible, and comes with a few more extras than the Haskell implementation, given JavaScript's type system limitations and the language's general flexibiity. 6 | 7 | [Maybe monad]: https://en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad 8 | 9 | ## Installation 10 | 11 | If you're intending to use this library from Node, you can just install it with NPM: 12 | 13 | $ npm install --save maybe-js 14 | 15 | For other environments, you can just grab `maybe.js` or `maybe.min.js` directly. 16 | 17 | Both will work with AMD, Node, or directly in the browser. 18 | 19 | ## Why 20 | 21 | `null` sucks. 22 | `undefined` sucks. 23 | Checking for either of them sucks. 24 | 25 | Why not make things a bit easier? 26 | 27 | ## Usage 28 | 29 | The tour: 30 | 31 | ```javascript 32 | var object = { 33 | attr: "hello", 34 | fn: function() { 35 | return this.attr + " world!"; 36 | } 37 | }; 38 | 39 | var wrapped = maybe(object); 40 | // => Maybe([object Object]) 41 | 42 | wrapped.value; 43 | // => object 44 | 45 | wrapped.get("attr"); 46 | // => Maybe(hello) 47 | 48 | wrapped.bind("fn"); 49 | // => Maybe(hello world!) 50 | 51 | wrapped.get("invalid_attr") 52 | // => Maybe(empty) 53 | 54 | wrapped.bind("invalid_fn") 55 | // => Maybe(empty) 56 | 57 | wrapped("attr"); 58 | // => Maybe(hello) 59 | 60 | wrapped("fn"); 61 | // => Maybe(hello world!) 62 | 63 | wrapped.tap(function(x) { 64 | x.attr = "bye" 65 | }).get("attr"); 66 | // => Maybe(bye) 67 | 68 | maybe().or("hello"); 69 | // => Maybe(hello) 70 | 71 | maybe().or(function() { return "generated value"; }); 72 | // => Maybe(generated value) 73 | 74 | maybe("hello").isNothing(); // => false 75 | maybe().isNothing(); // => true 76 | 77 | maybe("hello").isValue(); // => true 78 | maybe().isValue(); // => false 79 | 80 | maybe.nothing; 81 | // => Maybe(empty) 82 | 83 | var stringify = maybe.lift(JSON.stringify) 84 | stringify([1, 2, 3]); 85 | // => Maybe([1,2,3]) 86 | ``` 87 | 88 | #### `maybe(value)` 89 | 90 | Wraps `value` in a Maybe. 91 | 92 | If `value` is undefined, the Maybe is said to be empty. 93 | 94 | ```javascript 95 | var value = "hello"; 96 | maybe(value); 97 | // => Maybe(hello) 98 | 99 | maybe(); 100 | // => Maybe(empty) 101 | ``` 102 | 103 | #### `maybe(value).value` 104 | 105 | Returns the value stored in the Maybe. 106 | 107 | ```javascript 108 | maybe("hello").value; 109 | // => "hello" 110 | 111 | maybe().value; 112 | // => undefined 113 | ``` 114 | 115 | #### `maybe(value).get(key)` 116 | 117 | Accesses a property on a Maybe, returning a new Maybe encapsulating the property. 118 | If no value is present, an empty Maybe is returned. 119 | 120 | ```javascript 121 | var obj = { attr: "hello" }; 122 | maybe(obj).get("attr"); 123 | // => Maybe(hello); 124 | 125 | maybe(obj).get("not_an_attr"); 126 | // => Maybe(empty); 127 | ``` 128 | 129 | #### `maybe(value).bind(fn, ...args)` 130 | 131 | Calls a function on a Maybe's value. 132 | The return value is wrapped in another Maybe. 133 | 134 | `fn` may be a `Function`, or a `String`. 135 | If it's a String, it's assumed to be a instance function of the value. 136 | If it's a Function, it's called with the Maybe's value prepending other arguments. 137 | 138 | If an instance function isn't present, an empty Maybe is returned. 139 | 140 | ```javascript 141 | var obj = { 142 | fn: function() { return "return value"; } 143 | }; 144 | 145 | function toString(thing) { 146 | return Object.prototype.toString.call(thing); 147 | } 148 | 149 | var wrapped = maybe(obj); 150 | 151 | wrapped.bind(toString); 152 | // => Maybe([object Object]) 153 | 154 | wrapped.bind("fn"); 155 | // => Maybe(return value) 156 | 157 | wrapped.bind("not_a_fn"); 158 | // => Maybe(empty) 159 | ``` 160 | 161 | #### `maybe(opt, ...args)` 162 | 163 | A shorthand for both of the above. 164 | 165 | If a function, or a string property name that resolves to a function, the function will be called. 166 | 167 | Otherwise, will attempt to return a wrapped property. 168 | 169 | ```javascript 170 | var obj = { 171 | attr: "hi", 172 | fn: function() { return "return value"; } 173 | }; 174 | 175 | function toString(thing) { 176 | return Object.prototype.toString.call(thing); 177 | } 178 | 179 | var wrapped = maybe(obj); 180 | 181 | wrapped("attr"); 182 | // => Maybe(hi) 183 | 184 | wrapped("fn"); 185 | // => Maybe(return value) 186 | 187 | wrapped(toString); 188 | // => Maybe([object Object]) 189 | 190 | wrapped("not_a_property"); 191 | // => Maybe(empty) 192 | ``` 193 | 194 | #### `maybe(value).tap(fn, thisArg)` 195 | 196 | Yields the Maybe's value for modification. 197 | Returns a Maybe with the modified value. 198 | 199 | Support an optional argument that will be the `this` value in the function. 200 | 201 | ```javascript 202 | var obj = { 203 | attr: "hello" 204 | }; 205 | 206 | maybe(obj).tap(function(x) { 207 | x.attr += " world!"; 208 | }).get("attr"); 209 | // => Maybe(hello world!) 210 | ``` 211 | 212 | #### `maybe(value).or(alternate)` 213 | 214 | If a Maybe is empty, returns a new Maybe with the specified value. 215 | 216 | ```javascript 217 | maybe(undefined).or("new value"); 218 | // => Maybe(new value) 219 | ``` 220 | 221 | #### `maybe(value).isNothing()` 222 | 223 | Checks if the Maybe is empty or not. 224 | Returns a boolean. 225 | 226 | ```javascript 227 | maybe("thing").isNothing() // => false 228 | maybe(undefined).isNothing() // => true 229 | ``` 230 | 231 | #### `maybe(value).isValue()` 232 | 233 | Checks if the Maybe has a value or not. 234 | Returns a boolean. 235 | 236 | ```javascript 237 | maybe("thing").isValue() // => true 238 | maybe(undefined).isValue() // => false 239 | ``` 240 | 241 | #### `maybe.nothing` 242 | 243 | An empty Maybe. 244 | 245 | #### `maybe.lift(fn)` 246 | 247 | Wraps a function such that its return value becomes a Maybe. 248 | 249 | ```javascript 250 | var stringify = maybe.lift(JSON.stringify) 251 | stringify([1, 2, 3]); 252 | // => Maybe([1,2,3]) 253 | ``` 254 | 255 | ## License 256 | 257 | MIT. For more details, see the `LICENSE` file. 258 | --------------------------------------------------------------------------------