1 | # functional-pipeline
|
2 |
|
3 | > Quickly chain method calls, property access and functions in natural left to right expression.
|
4 |
|
5 | It is like [_.invoke](http://lodash.com/docs#invoke) + [_.pluck](http://lodash.com/docs#pluck) +
|
6 | [_.compose](http://lodash.com/docs#compose) all rolled into single function.
|
7 |
|
8 |
|
9 | [![NPM][functional-pipeline-icon] ][functional-pipeline-url]
|
10 |
|
11 | [![Build status][functional-pipeline-ci-image] ][functional-pipeline-ci-url]
|
12 | [![Coverage Status][functional-pipeline-coverage-image]][functional-pipeline-coverage-url]
|
13 | [![dependencies][functional-pipeline-dependencies-image] ][functional-pipeline-dependencies-url]
|
14 | [![devdependencies][functional-pipeline-devdependencies-image] ][functional-pipeline-devdependencies-url]
|
15 |
|
16 | Install and use under Node:
|
17 |
|
18 | ```
|
19 | npm install functional-pipeline --save
|
20 | var fp = require('functional-pipeline');
|
21 | // or
|
22 | global.fpDebug = true;
|
23 | var fp = require('functional-pipeline');
|
24 | // loads debug version of functional pipeline with
|
25 | // stricter argument checking and better error reporting
|
26 | ```
|
27 |
|
28 | Install and use in browser using bower:
|
29 |
|
30 | ```
|
31 | bower install functional-pipeline
|
32 | <script src="bower_components/functional-pipeline/dist/fp.js"></script>
|
33 | // or
|
34 | <script src="bower_components/functional-pipeline/dist/fp-debug.js"></script>
|
35 | // attaches as window.fp object
|
36 | ```
|
37 |
|
38 | ## Example
|
39 |
|
40 | Assuming this common code:
|
41 |
|
42 | ```js
|
43 | function triple(x) { return 3 * x; }
|
44 | function add2(x) { return x + 2; }
|
45 | var data = {
|
46 | age: 10,
|
47 | getAge: function () { return this.age; }
|
48 | };
|
49 | ```
|
50 |
|
51 | You can express in single line
|
52 |
|
53 | ```js
|
54 | var pipeline = fp('getAge', triple, add2);
|
55 | ```
|
56 |
|
57 | what usually takes several compositions that are read from inside out
|
58 |
|
59 | ```js
|
60 | function pipeline(obj) {
|
61 | return add2(triple(obj.getAge()));
|
62 | }
|
63 | ```
|
64 |
|
65 | You can even use nested paths using '.' separator
|
66 |
|
67 | ```js
|
68 | var obj = {
|
69 | a: {
|
70 | b: {
|
71 | c: ['foo', 'bar', 'baz']
|
72 | }
|
73 | }
|
74 | };
|
75 | fp('a.b.c.1')(obj); // 'bar'
|
76 | ```
|
77 |
|
78 | During runtime, detailed meta information is available in the `fp.version` object.
|
79 |
|
80 | ## Why?
|
81 |
|
82 | Clear code is good code. Functional pipeline makes reading code slightly easier:
|
83 |
|
84 | * Reading chained pipeline left to right is natural.
|
85 | * Having no branches (they should be encapsulated inside functions) lowers complexity
|
86 | * Composing multiple small functions makes code easier to test
|
87 |
|
88 | I especially recommend using functional pipelines for callbacks,
|
89 | replacing code with prebuilt and testable pipelines.
|
90 |
|
91 | Example: lets sort objects in the collection by date nested 2 levels deep. This is
|
92 | typical of the code arriving from the back end for example. Below
|
93 | each operation I am showing its order in the callback function. The actual
|
94 | argument name *item* does not matter.
|
95 |
|
96 | ```js
|
97 | items = _.sortBy(items, function(item) {
|
98 | return new Date(item.latest_event.datetime);
|
99 | ------ -------- ------------ --------
|
100 | 4 3 1 2
|
101 | });
|
102 | ```
|
103 |
|
104 | 1. Grab `latest_event` property
|
105 | 2. Grab `datetime` property
|
106 | 3. Create `new Date` object from result of the previous step
|
107 | 4. Return the created date.
|
108 |
|
109 | Reading the steps left to right starting in the middle, then switching
|
110 | to reading them right to left going back at the start of the line is unnatural to me.
|
111 | Here is the same sequence using *functional-pipeline*. The return operation is implicit.
|
112 |
|
113 | ```js
|
114 | function newDate(a) { return new Date(a); }
|
115 | items = _.sortBy(items, fp('latest_event', 'datetime', newDate));
|
116 | -------------- ---------- -------
|
117 | 1 2 3
|
118 | ```
|
119 |
|
120 | We had to create a utility function *newDate* to get around JavaScript's `new` keyword.
|
121 | You could use my [d3-helpers](https://github.com/bahmutov/d3-helpers) library that
|
122 | provides both functional pipeline and a few small utility functions like `newDate`.
|
123 |
|
124 | ## Debug mode
|
125 |
|
126 | Creating chains of calls dynamically using strings instead of
|
127 | actual functions makes debugging harder. Functional pipeline has second implementation
|
128 | with debugging turned on. This adds stringent checks before calling a method, or
|
129 | executing a function or returning a non-existent property. For example:
|
130 |
|
131 | ### Checks during pipeline construction
|
132 |
|
133 | *functional-pipeline debug* script first performs existance check during pipeline
|
134 | construction, making sure every argument is either a string or a valid function.
|
135 | If not, a detailed error message is thrown.
|
136 |
|
137 | ```js
|
138 | global.fpDebug = true;
|
139 | var fp = require('functional-pipeline');
|
140 | var f = fp('foo', bar); // bar is non existent function
|
141 | // throws right away
|
142 | Error: Invalid arguments to functional pipeline - not a string or function
|
143 | 0: string foo
|
144 | 1: undefined argument
|
145 | ```
|
146 |
|
147 | ### Checks during pipeline execution
|
148 |
|
149 | There are also checks during pipeline execution, throwing detailed error message
|
150 | if something goes wrong. Both the pipeline setup and the original object is
|
151 | in the exception's message in addition to the immediate error cause.
|
152 |
|
153 | ```js
|
154 | var obj = {
|
155 | bar: {
|
156 | baz: 'baz'
|
157 | }
|
158 | };
|
159 | var pipeline = fp('bar', 'foo');
|
160 | pipeline(obj);
|
161 |
|
162 | Error: Cannot use property foo from object {
|
163 | "baz": "baz"
|
164 | }
|
165 | pipeline
|
166 | 0: string bar
|
167 | 1: string foo
|
168 | original object
|
169 | {
|
170 | "bar": {
|
171 | "baz": "baz"
|
172 | }
|
173 | }
|
174 | ```
|
175 |
|
176 | ## Inspiration
|
177 |
|
178 | I was inspired by [l33teral](https://github.com/nicholascloud/l33teral) library for
|
179 | convenient access to properties deep inside object hierarchy. My second inspiration
|
180 | was [Make tests read like a book](http://uxebu.com/blog/2013/01/08/make-tests-read-like-a-book/) by
|
181 | Wolfram Kriesing.
|
182 |
|
183 | Related: [functional-extract](https://github.com/bahmutov/functional-extract)
|
184 |
|
185 | ### Small print
|
186 |
|
187 | Author: Gleb Bahmutov © 2014
|
188 |
|
189 | * [@bahmutov](https://twitter.com/bahmutov)
|
190 | * [glebbahmutov.com](http://glebbahmutov.com)
|
191 | * [blog](http://glebbahmutov.com/blog/)
|
192 |
|
193 | License: MIT - do anything with the code, but don't blame me if it does not work.
|
194 |
|
195 | Spread the word: tweet, star on github, etc.
|
196 |
|
197 | Support: if you find any problems with this module, email / tweet /
|
198 | [open issue](https://github.com/bahmutov/functional-pipeline/issues) on Github
|
199 |
|
200 | ## MIT License
|
201 |
|
202 | Copyright (c) 2014 Gleb Bahmutov
|
203 |
|
204 | Permission is hereby granted, free of charge, to any person
|
205 | obtaining a copy of this software and associated documentation
|
206 | files (the "Software"), to deal in the Software without
|
207 | restriction, including without limitation the rights to use,
|
208 | copy, modify, merge, publish, distribute, sublicense, and/or sell
|
209 | copies of the Software, and to permit persons to whom the
|
210 | Software is furnished to do so, subject to the following
|
211 | conditions:
|
212 |
|
213 | The above copyright notice and this permission notice shall be
|
214 | included in all copies or substantial portions of the Software.
|
215 |
|
216 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
217 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
218 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
219 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
220 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
221 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
222 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
223 | OTHER DEALINGS IN THE SOFTWARE.
|
224 |
|
225 | [functional-pipeline-icon]: https://nodei.co/npm/functional-pipeline.png?downloads=true
|
226 | [functional-pipeline-url]: https://npmjs.org/package/functional-pipeline
|
227 | [functional-pipeline-ci-image]: https://travis-ci.org/bahmutov/functional-pipeline.png?branch=master
|
228 | [functional-pipeline-ci-url]: https://travis-ci.org/bahmutov/functional-pipeline
|
229 | [functional-pipeline-coverage-image]: https://coveralls.io/repos/bahmutov/functional-pipeline/badge.png
|
230 | [functional-pipeline-coverage-url]: https://coveralls.io/r/bahmutov/functional-pipeline
|
231 | [functional-pipeline-dependencies-image]: https://david-dm.org/bahmutov/functional-pipeline.png
|
232 | [functional-pipeline-dependencies-url]: https://david-dm.org/bahmutov/functional-pipeline
|
233 | [functional-pipeline-devdependencies-image]: https://david-dm.org/bahmutov/functional-pipeline/dev-status.png
|
234 | [functional-pipeline-devdependencies-url]: https://david-dm.org/bahmutov/functional-pipeline#info=devDependencies
|