├── README.md ├── package.json └── µ-ffsm.js /README.md: -------------------------------------------------------------------------------- 1 | # µ-ffsm: Micro fluent API helper 2 | 3 | Tiny helper function to create fluent interfaces/APIs in a somewhat consistent way. 4 | 5 | > npm i mu-ffsm 6 | 7 | ## Quickstart 8 | 9 | Use it 10 | 11 | var mkChained = require('mu-ffsm'); 12 | 13 | The idea is that you construct a fluent builder with some initial state 14 | of type `S` by calling an entry function. Then each chained call 15 | transitions the state to a new state. 16 | 17 | The value you get from chaining a bunch of calls can be executed as a 18 | function, which calls exit to construct a value from that state and any 19 | options you pass in. 20 | 21 | - entry : * ⟶ S 22 | - transition : (S ⟶ *) ⟶ S 23 | - exit : S ⟶ (* ⟶ *) 24 | 25 | For example 26 | 27 | ```js 28 | var API = mkChained({ 29 | 0: function(opt) {return ;/* create initial state */}, 30 | then: function(s, opt) {return s; /* new state */}, 31 | whut: function(s, opt) {return s; /* new state */}, 32 | 1: function(s, opt) {return ;/* compute final value */} 33 | }); 34 | ``` 35 | 36 | So `0`, `1` are entry, exit functions. All other functions transition an internal state. 37 | All functions can take arguments, eg. `opt` 38 | 39 | We create an instance of our newly crafted API, 40 | 41 | ``` 42 | var call = API() // entry 43 | .whut() // transition 44 | .then() // transition 45 | .whut(); // transition 46 | ``` 47 | 48 | And call it 49 | 50 | ``` 51 | var result0 = call() // exit 52 | , result1 = call() // exit 53 | ``` 54 | 55 | ## Concrete example 56 | 57 | 58 | Import 59 | 60 | var FFSM = require('mu-ffsm'); 61 | 62 | Create language/machine. 63 | 64 | ```js 65 | // internal state is an Array 66 | var Talker = FFSM({ 67 | 0: function() { return []; }, // TODO allow const 68 | talk: function(say, what) { say.push(what); return say; }, 69 | 1: function(say, sep) { return say.join(sep || ' '); } 70 | }); 71 | ``` 72 | 73 | Construct sentences/instances: 74 | 75 | ```js 76 | var cowboyGreeting = Talker() 77 | .talk('howdy') 78 | .talk('cowboy'); 79 | 80 | // make dramatic 81 | console.log(cowboyGreeting(', ...')); 82 | ``` 83 | 84 | ## How the chaining works 85 | 86 | What you define 87 | 88 | ```js 89 | var M = FFSM({ 90 | 0: function(i) { return /* initial state */; } // entry function 91 | a: function(s, t) { return /* new state */; } // transition 'a' 92 | b: function(s, t) { return /* new state */; } // transition 'b' 93 | 1: function(s, x) { return /* final value */; } // exit function 94 | }); 95 | ``` 96 | 97 | What gets called with which arguments 98 | 99 | ```js 100 | var i = M(entry) // x : S ← M.0(entry) 101 | .a(trigger_0) // y : S ← M.a(x, trigger_0) 102 | .b(trigger_1) // z : S ← M.b(y, trigger_1) 103 | .a(trigger_2); // i : S ← M.a(z, trigger_2) 104 | ``` 105 | 106 | Finally 107 | 108 | ```js 109 | var y = i(x); // y ← M.1(i, x) 110 | ``` 111 | 112 | So we have 113 | 114 | - First `M(entry)` creates a new machine instance of type M. 115 | It's initial state derived computed as `0(entry)`. 116 | 117 | - Then `.a(trigger_0)` transitions the machine with transition `a` to a new 118 | state, using the previous state and the data from `trigger_0` to 119 | compute the new state. 120 | 121 | - Similarly `.b(t_1)`, `.a(t_2)`. 122 | 123 | - Finally, the `i(x)` call constructs an element out of 124 | the internal state and the argument using the exit function `1(i,x)`. 125 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mu-ffsm", 3 | "version": "0.0.2", 4 | "description": "Fluent functional state machines", 5 | "main": "µ-ffsm.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Jelle 'wires' Herold ", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /µ-ffsm.js: -------------------------------------------------------------------------------- 1 | 2 | // See README.md 3 | 4 | module.exports = function(spec) { 5 | return function(init) { 6 | var s = spec[0] ? spec[0](init) : 0; 7 | 8 | var i = function(opt) { 9 | return spec[1] ? spec[1](s, opt) : s; 10 | } 11 | 12 | Object.keys(spec).forEach( 13 | function(name){ 14 | // skip `entry` and `exit` functions 15 | if(/^\d+$/.test(name)) 16 | return; 17 | 18 | // transition 'name : (s, opt) -> s' 19 | i[name] = function(opt) { 20 | s = spec[name](s, opt); 21 | return i; 22 | }; 23 | }); 24 | 25 | return i; 26 | } 27 | }; 28 | 29 | --------------------------------------------------------------------------------