• Stars
    star
    1,049
  • Rank 43,889 (Top 0.9 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

The most powerful IO monad implementation in JS, possibly in any language!

Monio

Build Status npm Module Coverage Status Modules License

Monio (mō'ne-yo) provides an async-capable IO Monad (including "do" style) for JS, with several helpful companion monads (like Maybe and Either) thrown in.

Monio's IO/IOx is the most powerful IO monad implementation in JS, possibly in any language!

Note: This is obviously a marketing claim, not a provable mathematical/scientific assertion. Nevertheless, I believe it's true!

Just("Welcome, Monads")
.concat(Just(" And Friends"))
.map(v => v.toUpperCase())
.fold(Maybe.from)
.map(v => v + "!")
.fold(
    () => IO.of("--empty--"),
    greetings => IO(() => console.log(greetings))
)
.run();
// WELCOME, MONADS AND FRIENDS!

Wait, What's A "Monad"?

If you're already comfortable with Functional Programming (FP), and you're at least familiar with monads, and if the code snippet above doesn't scare you away, skip to "See Monio In Action". Or, if you're just looking for quick code examples of all the monads in Monio, check 'em out here!

But if terms like "higher-order function" or "monad" are confusing or intimidating to you, I encourage you to take some time first to build some comfort and familiarity; these are the foundations you need to get the most out of Monio.

I don't want you to feel intimidated. This isn't going to require you to speak in mathematical notation or re-learn everything you thought you knew about programming.

Well... to be clear, what I mean is, you don't have to do all that just to get started. There are small and comfortable baby steps you can begin taking to kick off the journey. The further you explore down the path, I think the more FP and monads will "hook" you, nudging and tugging you into different ways of thinking about and solving problems.



Now, it's time to dive into FP... and then Monads. But if you're already comfortable with FP, and ready to tackle learning monads specifically, you can jump directly to "Expansive Intro To Monads".

Make sure you take your time reading through that guide; it's pretty long and detailed, and there's a lot to digest.



See Monio In Action

To see how writing Monio-based code looks and feels, especially the use of IO/IOx monads (and all its variations), check out these demos. There's a variety of different approaches/styles demonstrated, which illustrate the flexibility Monio's monads offer.

Here's a full demo app (client and server) featuring Monio:

Monio Demo App

In addition, here's some live codepen demos:


Check out Monio's Monads for more example code snippets and detailed descriptions.

Overview

Monio balances the power of monads -- often dismissed by the non-FP programmer as academic and convoluted -- while pragmatically embracing the reality of the vast majority of JS programs: paradigm mixture (some OO, some FP, and probably a lot of imperative procedural code).

The driving inspiration behind Monio is the IO monad -- useful for managing side-effects -- that additionally supports "do-style" syntax with JS-ergonomic asynchrony (based on promises) in the style of familiar async..await code. IOs are lazy, so their operations are not triggered until the run(..) method is called.

Monio's IO is a transformer over promises, which means that when promises are produced in an IO, they are automatically unwrapped; of course, that means subsequent IO operations are deferred. If any IO in a chain produces a promise, run(..)'s result will be "lifted" to a promise that resolves when the entire IO chain is complete. Otherwise, the IO instance and its run(..) call will operate synchronously and immediately produce the result.

Monio intentionally chooses to model asynchrony over promises instead of Future monads, because of its goal of balancing FP with pragmatic and idomatic non-FP JS. However, there's nothing that should prevent you from using a Future monad with Monio if you prefer.

IO's "do-style" syntax is specified with the do(..) method (automatically lifts the IO to promise-producing asynchrony), which accepts JS generators (including "async generators": async function *whatever(){ .. }). yield is used for chaining IOs (which can produce promises to defer), whereas await is for explicitly deferring on a promise that's not already wrapped in an IO. The resulting style of code should be more broadly approachable for JS developers, while still benefitting from monads.

IO's do(..) is JS-ergonomic for exception handling: uncaught JS exceptions become promise rejections, and IO-produced promise rejections are try..catch'able. IO also supports modeling exception handling through Either monads: doEither(..) transforms uncaught exceptions into Either:Left values, and recognizes IO-produced Either:Left values as try..catch'able exceptions.

Monio's IO is also a Reader monad, which carries side-effect read environments alongside IO operations.

For the FP savvy

Monio's IO models a function e => IO a (Promise b c), which is strong enough to capture (optional) environment passing, side effects, async, and error handling without the pain of composing each type separately.

Typically IO does not take an argument, but given one, it acts like an effectful Reader. In addition, it can model sync or async functions so the inner Promise becomes optional.

In that way, you can think of it as ReaderT (IOT (Promise|Identity a b)) where Promise gets swapped for Identity if you're not doing async.

Monio's IO is like Scala's ZIO / RIO, where we have all the functionality we need wrapped up in a single monad kind.

Monio's Monads

Just("Are you ready for Monio?");

Check out Monio's Monads for a bunch of code snippets and detailed descriptions of all the monads (and helpers) provided in this library.

Using Monio

npm Module Module Format

To use monads/helpers from Monio, require(..) or import .. them by name:

  • CJS programs/modules in Node (requires Node v12+):

    var { Maybe, IO } = require("monio");
    
    // or:
    var Just = require("monio/just");
  • ESM in Node (requires Node v14+):

    import { Maybe, IO } from "monio";
    
    // or:
    import Just from "monio/just";

    Note: As of v0.32.0, the previously deprecated ESM import specifier segment /esm in Monio import paths has been removed, in favor of unified import specifier paths via Node Conditional Exports. For ESM import statements, use the specifier style "monio" or "monio/just".

  • ESM in browser (requires browser with ES6+ support):

    import { Maybe, IO } from "/path/to/monio/dist/esm/index.mjs";
    
    // or:
    import Just from "/path/to/monio/dist/esm/just.mjs";

    It's clearly unfortunate/inconvenient to have to specify URL paths (including filename extensions!) to import Monio entities when using ESM-format in the browser. This is an artifact of how browsers currently support ESM.

    The current best-option work-around is to use an Import-Map (if your target browser environment supports them) on your site to create friendlier import-path names that the browser then maps to the required URL paths.

    To use an Import-Map, include an inline <script type="importmap">..</script> block in each HTML page of your site, with the Import-Map JSON contents inside the block.

    If you'd like to use Monio ESM-format in the browser with the same friendly import names that are available for Node import / require, and if Import-Maps are supported in your target browser environment, you can start with the contents of the provided dist/esm/import-map-template.json file

    <!-- your HTML page -->
    
    <script type="importmap">
    {
      "imports": {
        "monio": "/monio/index.mjs",
        "monio/just": "/monio/just.mjs",
        "monio/nothing": "/monio/nothing.mjs",
        // ..
        "monio/util": "/monio/lib/util.mjs"
     }
    }
    </script>
    
    <!-- your HTML page -->

    As you can see in the above snippet, the assumed paths in this template file all expect Monio's dist/esm directory to be deployed at the root of your site as /monio/*. Adjust those paths as necessary to match your site's deployment structure.

    • Important Note:

      Whether you use an Import-Map (as just described) or not, since ESM-format files are not bundled together, but loaded separately, the dist/esm files internally use relative import paths that reference each other. This directory/filename structure must be preserved for ESM-format Monio to operate in the browser as distributed. As such, make sure the entire contents of the dist/esm directory are deployed to your site exactly as provided.

      If that is not possible, you may use an Import-Map (if your target browser environment supports them) to adapt the original relative ESM import paths to a different deployment structure and/or file-naming. You could also use Import-Remap to force-rewrite the import paths to different values.

  • UMD in browser (requires browser with ES5+ support):

    <script src="/path/to/monio/dist/umd/bundle.js"></script>
    
    <!-- or -->
    <script src="/path/to/monio/dist/umd/just.js"></script>

    The UMD bundle.js file alone is sufficient for Monio deployment if using the UMD format. Rename that bundle.js file (e.g., monio.js) and deploy wherever is appropriate in your site. You could deploy and load the individual UMD files if you prefer, but it's generally easier and more efficient to use the single bundle file.

Once Monio monads are imported into your module/program, instances are created from functions (no new constructors necessary):

var helloWorld = Just("Hello World");

helloWorld._inspect();
// Just("Hello World")

Just.is(helloWorld);
// true

Monio's monads can of course be used together in various expected ways:

var helloWorld = Just("Hello World");
var greeting = Maybe(helloWorld);
var log = str => IO(() => console.log(str));

var main = IO.do(function *main(){
    var msg = greeting.map(m => `${ m }!!`);

    // Uncomment this line to swap in an empty maybe
    // msg = Maybe.from(null);

    yield msg.fold(IO.of,log);
});

main.run();
// Hello World!!

Import/Require Paths For Monio Entities

Module Format

Note: For ESM-format usage in the browser, URL paths (with filename extensions!) or Import-Maps are required. To use the friendlier ESM-Node/CJS names while in the browser, an Import-Map is required. See above for explanation.

Here are the individual import / require named-entity paths:

  • Monio Index/Namespace (includes all named top-level entities): "monio"

    ESM (Node): import * as Monio from "monio"

    ESM (Node): import { Just, Maybe, IO } from "monio"

    ESM (Browser): import * as Monio from "/path/to/monio/dist/esm/index.mjs"

    ESM (Browser): import { Just, Maybe, IO } from "/path/to/monio/dist/esm/index.mjs"

    CJS: const Monio = require("monio")

    CJS: const { Just, Maybe, IO } = require("monio")

  • Just: "monio/just"

    ESM (Node): import Just from "monio/just"

    ESM (Browser): import Just from "/path/to/monio/dist/esm/just.mjs"

    CJS: const Just = require("monio/just")

  • Nothing: "monio/nothing"

    ESM (Node): import Nothing from "monio/nothing"

    ESM (Browser): import Nothing from "/path/to/monio/dist/esm/nothing.mjs"

    CJS: const Nothing = require("monio/nothing")

  • Maybe: "monio/maybe"

    ESM (Node): import Maybe from "monio/maybe"

    ESM (Node): import Maybe from "/path/to/monio/dist/esm/maybe.mjs"

    CJS: const Maybe = require("monio/maybe")

  • Either: "monio/either"

    ESM (Node): import Either from "monio/either"

    ESM (Node): import Either from "/path/to/monio/dist/esm/either.mjs"

    CJS: const Either = require("monio/either")

  • AsyncEither: "monio/async-either"

    ESM (Node): import AsyncEither from "monio/async-either"

    ESM (Browser): import AsyncEither from "/path/to/monio/dist/esm/async-either.mjs"

    CJS: const AsyncEither = require("monio/async-either")

  • IO: "monio/io"

    ESM (Node): import IO from "monio/io"

    ESM (Browser): import IO from "/path/to/monio/dist/esm/io/io.mjs"

    CJS: const IO = require("monio/io")

  • IOx: "monio/iox" or "monio/io/x"

    ESM (Node): import IOx from "monio/iox"

    ESM (Browser): import IOx from "/path/to/monio/dist/esm/io/iox.mjs"

    CJS: const IOx = require("monio/iox")

  • AnyIO: "monio/io/any"

    ESM (Node): import AnyIO from "monio/io/any"

    ESM (Browser): import AnyIO from "/path/to/monio/dist/esm/io/any.mjs"

    CJS: const AnyIO = require("monio/io/any")

  • AllIO: "monio/io/all"

    ESM (Node): import AllIO from "monio/io/all"

    ESM (Browser): import AllIO from "/path/to/monio/dist/esm/io/all.mjs"

    CJS: const AllIO = require("monio/io/all")

  • IOHelpers (named helpers for IO): "monio/io/helpers"

    ESM (Node): import * as IOHelpers from "monio/io/helpers"

    ESM (Node): import { waitAll, match, applyIO } from "monio/io/helpers"

    ESM (Browser): import * as IOHelpers from "/path/to/monio/dist/esm/io/helpers.mjs"

    ESM (Browser): import { waitAll, match, applyIO } from "/path/to/monio/dist/esm/io/helpers.mjs"

    CJS: const IOHelpers = require("monio/io/helpers")

    CJS: const { waitAll, match, applyIO } = require("monio/io/helpers")

  • IOxHelpers (named helpers for IOx): "monio/iox/helpers" or "monio/io/x-helpers"

    ESM (Node): import * as IOxHelpers from "monio/iox/helpers"

    ESM (Node): import { filterIn, distinct, waitFor } from "monio/iox/helpers"

    ESM (Node): import * as IOxHelpers from "/path/to/monio/dist/esm/io/x-helpers.mjs"

    ESM (Node): import { filterIn, distinct, waitFor } from "/path/to/monio/dist/esm/io/x-helpers.mjs"

    CJS: const IOHelpers = require("monio/iox/helpers")

    CJS: const { filterIn, distinct, waitFor } = require("monio/iox/helpers")

  • MonioUtil (helper utils for Monio): "monio/util"

    ESM (Node): import * as MonioUtil from "monio/util"

    ESM (Node): import { curry, fold } from "monio/util"

    ESM (Browser): import * as MonioUtil from "/path/to/monio/dist/esm/lib/util.mjs"

    ESM (Browser): import { curry, fold } from "/path/to/monio/dist/esm/lib/util.mjs"

    CJS: const MonioUtil = require("monio/util")

    CJS: const { curry, fold } = require("monio/util")

Tests

A test suite is included in this repository, as well as the npm package distribution. The default test behavior runs the test suite using the files in src/.

  1. The tests are run with QUnit.

  2. To run the test utility with npm:

    npm test
    

Test Coverage

Coverage Status

If you have NYC (Istanbul) already installed on your system (requires v14.1+), you can use it to check the test coverage:

npm run coverage

Then open up coverage/lcov-report/index.html in a browser to view the report.

Note: The npm script coverage:report is only intended for use by project maintainers. It sends coverage reports to Coveralls.

License

License

All code and documentation are (c) 2022 Kyle Simpson and released under the MIT License. A copy of the MIT License is also included.

More Repositories

1

You-Dont-Know-JS

A book series on JavaScript. @YDKJS on twitter.
178,746
star
2

Functional-Light-JS

Pragmatic, balanced FP in JavaScript. @FLJSBook on twitter.
JavaScript
16,593
star
3

LABjs

Loading And Blocking JavaScript: On-demand parallel loader for JavaScript with execution order dependencies
HTML
2,277
star
4

asynquence

Asynchronous flow control (promises, generators, observables, CSP, etc)
JavaScript
1,743
star
5

CAF

Cancelable Async Flows (CAF)
JavaScript
1,332
star
6

TNG-Hooks

Provides React-inspired 'hooks' like useState(..) for stand-alone functions
JavaScript
1,011
star
7

native-promise-only

A polyfill for native ES6 Promises as close as possible (no extensions) to the strict spec definitions.
JavaScript
723
star
8

A-Tale-Of-Three-Lists

Comparing various async patterns for a single demo
JavaScript
651
star
9

fasy

FP iterators that are both eager and asynchronous
JavaScript
546
star
10

FPO

FP library for JavaScript. Supports named-argument style methods.
JavaScript
449
star
11

youperiod.app

YouPeriod.app -- the privacy-first period tracking app
JavaScript
442
star
12

JSON.minify

Simple minifier for JSON to remove comments and whitespace
409
star
13

TypL

The Type Linter for JS
JavaScript
374
star
14

h5ive-DEPRECATED

**DEPRECATED** A collection of thin facade APIs wrapped around HTML5 JavaScript features.
JavaScript
324
star
15

eslint-plugin-proper-arrows

ESLint rules to ensure proper arrow function definitions
JavaScript
305
star
16

foi-lang

Foi: a different kind of functional programming language
JavaScript
303
star
17

grips

Simple-logic templates
JavaScript
287
star
18

moduloze

Convert CommonJS (CJS) modules to UMD and ESM formats
JavaScript
207
star
19

ES-Feature-Tests

Feature Tests for JavaScript
JavaScript
199
star
20

let-er

DEPRECATED: Transpile non-ES6 let-blocks into ES6 (or ES3)
JavaScript
192
star
21

Incomplete-JS

"The Incomplete Guide to JavaScript" (book). @IncompleteJS on twitter.
190
star
22

revocable-queue

Specialized async queue data structure, supports revocation of values
JavaScript
176
star
23

deePool

highly-efficient pool for JavaScript objects
JavaScript
118
star
24

getify.github.io

JavaScript
113
star
25

concrete-syntax-tree

Defining a standard JavaScript CST (concrete syntax tree) to complement ASTs
107
star
26

eslint-plugin-proper-ternary

ESLint rules to ensure proper usage of ternary/conditional expressions
JavaScript
96
star
27

cloud-sweeper

A casual game built for the web.
JavaScript
94
star
28

BikechainJS

JavaScript VM engine (powered by V8); server-side environment modules; server-side synchronous web app controllers
C++
82
star
29

wepuzzleit

Demo PoC game for various advanced HTML5 js APIs
JavaScript
79
star
30

workshop-periodic-table

66
star
31

elasi

EL/ASI: Encrypt Locally, Account Secure Interchange
JavaScript
62
star
32

remote-csp-channel

Remote bridge for CSP channels
JavaScript
55
star
33

ScanTree

Scan a JS file tree to build an ordered and grouped dependency listing
JavaScript
51
star
34

dwordly-game

A game where words dwindle down to the shortest possible
JavaScript
42
star
35

stable-timers

timers with less race conditions
JavaScript
38
star
36

emdash

Simple blogging with node/iojs + GitHub.
36
star
37

domio

DOM and Event helpers for Monio
JavaScript
30
star
38

eslint-plugin-no-optional-call

ESLint plugin to disallow the optional-call operator
JavaScript
30
star
39

eslint-plugin-arrow-require-this

DEPRECATED: ESLint rule to require arrow functions to reference the 'this' keyword
JavaScript
28
star
40

import-remap

Rewrite ES module import specifiers using an import-map.
JavaScript
25
star
41

gum-try-hd

Try to enforce HD (16:9) camera aspect ratio for web-video calls
JavaScript
25
star
42

Mock-DOM-Resources

A mock of (parts of) the DOM API to simulate resource preloading and loading
JavaScript
25
star
43

make-a-game

some project files for a tutorial on making a simple web game
JavaScript
25
star
44

mpAjax

framework plugin for handling multi-part Ajax responses
JavaScript
23
star
45

asyncGate.js

DEPRECATED. asynchronous gate for javascript
JavaScript
21
star
46

tic-tac-toe-workshop

Workshop files for building tic-tac-toe in JS and <canvas>
21
star
47

esre

esre: fully configurable JS code formatting
20
star
48

workshop-chess-diagonals

17
star
49

featuretests.io

JavaScript Feature Tests... as a service
JavaScript
16
star
50

FoilScript

FoilScript: a new dialect of JS that fixes the sucky parts but still looks and feels like JS
16
star
51

literalizer

Specialized heuristic lexer for JS to identify complex literals
JavaScript
16
star
52

normalize-selector

Normalize CSS selectors
JavaScript
14
star
53

DOMEventBridge

Bridge DOM events to a JS event hub (for pubsub)
JavaScript
14
star
54

pong-around-workshop

Workshop files for building a pong-variant game in JS and <canvas>
12
star
55

workshop-wordy-unscrambler

11
star
56

shortie.me

Proof-of-concept demo for server-side JavaScript driven "middle-end" architecture
JavaScript
11
star
57

workshop-knights-dialer

8
star
58

middleend-boilerplate

Boilerplate Starting Point for Middle-end Web Architecture
JavaScript
8
star
59

demo-app-weatheround

JavaScript
7
star
60

the-economy-of-keystrokes-slides

Slides code built for "The Economy of Keystrokes" talk
JavaScript
6
star
61

santa-connect-app

Santa Connect: keeping track of your kids' nice/naughty status
JavaScript
5
star
62

nyc-bug-demo

bug demo for NYC code coverage tool
JavaScript
4
star
63

unnamed

unnamed (for now). nothing to see here. ;-)
JavaScript
2
star
64

my-lifesheets

CSS
1
star
65

breakthewebforward.com

JavaScript
1
star