UNPKG

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