UNPKG

9.17 kBJavaScriptView Raw
1"use strict";
2/**
3 * Higher order functions
4 *
5 * These utility functions are exported, but are subject to change without notice.
6 *
7 * @packageDocumentation
8 */
9var __spreadArrays = (this && this.__spreadArrays) || function () {
10 for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
11 for (var r = Array(s), k = 0, i = 0; i < il; i++)
12 for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
13 r[k] = a[j];
14 return r;
15};
16Object.defineProperty(exports, "__esModule", { value: true });
17exports.pattern = exports.invoke = exports.val = exports.eq = exports.is = exports.any = exports.all = exports.or = exports.and = exports.not = exports.parse = exports.propEq = exports.prop = exports.pipe = exports.compose = exports.curry = void 0;
18/**
19 * Returns a new function for [Partial Application](https://en.wikipedia.org/wiki/Partial_application) of the original function.
20 *
21 * Given a function with N parameters, returns a new function that supports partial application.
22 * The new function accepts anywhere from 1 to N parameters. When that function is called with M parameters,
23 * where M is less than N, it returns a new function that accepts the remaining parameters. It continues to
24 * accept more parameters until all N parameters have been supplied.
25 *
26 *
27 * This contrived example uses a partially applied function as an predicate, which returns true
28 * if an object is found in both arrays.
29 * @example
30 * ```
31 * // returns true if an object is in both of the two arrays
32 * function inBoth(array1, array2, object) {
33 * return array1.indexOf(object) !== -1 &&
34 * array2.indexOf(object) !== 1;
35 * }
36 * let obj1, obj2, obj3, obj4, obj5, obj6, obj7
37 * let foos = [obj1, obj3]
38 * let bars = [obj3, obj4, obj5]
39 *
40 * // A curried "copy" of inBoth
41 * let curriedInBoth = curry(inBoth);
42 * // Partially apply both the array1 and array2
43 * let inFoosAndBars = curriedInBoth(foos, bars);
44 *
45 * // Supply the final argument; since all arguments are
46 * // supplied, the original inBoth function is then called.
47 * let obj1InBoth = inFoosAndBars(obj1); // false
48 *
49 * // Use the inFoosAndBars as a predicate.
50 * // Filter, on each iteration, supplies the final argument
51 * let allObjs = [ obj1, obj2, obj3, obj4, obj5, obj6, obj7 ];
52 * let foundInBoth = allObjs.filter(inFoosAndBars); // [ obj3 ]
53 *
54 * ```
55 *
56 * @param fn
57 * @returns {*|function(): (*|any)}
58 */
59function curry(fn) {
60 return function curried() {
61 if (arguments.length >= fn.length) {
62 return fn.apply(this, arguments);
63 }
64 var args = Array.prototype.slice.call(arguments);
65 return curried.bind.apply(curried, __spreadArrays([this], args));
66 };
67}
68exports.curry = curry;
69/**
70 * Given a varargs list of functions, returns a function that composes the argument functions, right-to-left
71 * given: f(x), g(x), h(x)
72 * let composed = compose(f,g,h)
73 * then, composed is: f(g(h(x)))
74 */
75function compose() {
76 var args = arguments;
77 var start = args.length - 1;
78 return function () {
79 var i = start, result = args[start].apply(this, arguments);
80 while (i--)
81 result = args[i].call(this, result);
82 return result;
83 };
84}
85exports.compose = compose;
86/**
87 * Given a varargs list of functions, returns a function that is composes the argument functions, left-to-right
88 * given: f(x), g(x), h(x)
89 * let piped = pipe(f,g,h);
90 * then, piped is: h(g(f(x)))
91 */
92function pipe() {
93 var funcs = [];
94 for (var _i = 0; _i < arguments.length; _i++) {
95 funcs[_i] = arguments[_i];
96 }
97 return compose.apply(null, [].slice.call(arguments).reverse());
98}
99exports.pipe = pipe;
100/**
101 * Given a property name, returns a function that returns that property from an object
102 * let obj = { foo: 1, name: "blarg" };
103 * let getName = prop("name");
104 * getName(obj) === "blarg"
105 */
106exports.prop = function (name) { return function (obj) { return obj && obj[name]; }; };
107/**
108 * Given a property name and a value, returns a function that returns a boolean based on whether
109 * the passed object has a property that matches the value
110 * let obj = { foo: 1, name: "blarg" };
111 * let getName = propEq("name", "blarg");
112 * getName(obj) === true
113 */
114exports.propEq = curry(function (name, _val, obj) { return obj && obj[name] === _val; });
115/**
116 * Given a dotted property name, returns a function that returns a nested property from an object, or undefined
117 * let obj = { id: 1, nestedObj: { foo: 1, name: "blarg" }, };
118 * let getName = prop("nestedObj.name");
119 * getName(obj) === "blarg"
120 * let propNotFound = prop("this.property.doesnt.exist");
121 * propNotFound(obj) === undefined
122 */
123exports.parse = function (name) { return pipe.apply(null, name.split('.').map(exports.prop)); };
124/**
125 * Given a function that returns a truthy or falsey value, returns a
126 * function that returns the opposite (falsey or truthy) value given the same inputs
127 */
128exports.not = function (fn) { return function () {
129 var args = [];
130 for (var _i = 0; _i < arguments.length; _i++) {
131 args[_i] = arguments[_i];
132 }
133 return !fn.apply(null, args);
134}; };
135/**
136 * Given two functions that return truthy or falsey values, returns a function that returns truthy
137 * if both functions return truthy for the given arguments
138 */
139function and(fn1, fn2) {
140 return function () {
141 var args = [];
142 for (var _i = 0; _i < arguments.length; _i++) {
143 args[_i] = arguments[_i];
144 }
145 return fn1.apply(null, args) && fn2.apply(null, args);
146 };
147}
148exports.and = and;
149/**
150 * Given two functions that return truthy or falsey values, returns a function that returns truthy
151 * if at least one of the functions returns truthy for the given arguments
152 */
153function or(fn1, fn2) {
154 return function () {
155 var args = [];
156 for (var _i = 0; _i < arguments.length; _i++) {
157 args[_i] = arguments[_i];
158 }
159 return fn1.apply(null, args) || fn2.apply(null, args);
160 };
161}
162exports.or = or;
163/**
164 * Check if all the elements of an array match a predicate function
165 *
166 * @param fn1 a predicate function `fn1`
167 * @returns a function which takes an array and returns true if `fn1` is true for all elements of the array
168 */
169exports.all = function (fn1) { return function (arr) { return arr.reduce(function (b, x) { return b && !!fn1(x); }, true); }; };
170// tslint:disable-next-line:variable-name
171exports.any = function (fn1) { return function (arr) { return arr.reduce(function (b, x) { return b || !!fn1(x); }, false); }; };
172/** Given a class, returns a Predicate function that returns true if the object is of that class */
173exports.is = function (ctor) { return function (obj) {
174 return (obj != null && obj.constructor === ctor) || obj instanceof ctor;
175}; };
176/** Given a value, returns a Predicate function that returns true if another value is === equal to the original value */
177exports.eq = function (value) { return function (other) { return value === other; }; };
178/** Given a value, returns a function which returns the value */
179exports.val = function (v) { return function () { return v; }; };
180function invoke(fnName, args) {
181 return function (obj) { return obj[fnName].apply(obj, args); };
182}
183exports.invoke = invoke;
184/**
185 * Sorta like Pattern Matching (a functional programming conditional construct)
186 *
187 * See http://c2.com/cgi/wiki?PatternMatching
188 *
189 * This is a conditional construct which allows a series of predicates and output functions
190 * to be checked and then applied. Each predicate receives the input. If the predicate
191 * returns truthy, then its matching output function (mapping function) is provided with
192 * the input and, then the result is returned.
193 *
194 * Each combination (2-tuple) of predicate + output function should be placed in an array
195 * of size 2: [ predicate, mapFn ]
196 *
197 * These 2-tuples should be put in an outer array.
198 *
199 * @example
200 * ```
201 *
202 * // Here's a 2-tuple where the first element is the isString predicate
203 * // and the second element is a function that returns a description of the input
204 * let firstTuple = [ angular.isString, (input) => `Heres your string ${input}` ];
205 *
206 * // Second tuple: predicate "isNumber", mapfn returns a description
207 * let secondTuple = [ angular.isNumber, (input) => `(${input}) That's a number!` ];
208 *
209 * let third = [ (input) => input === null, (input) => `Oh, null...` ];
210 *
211 * let fourth = [ (input) => input === undefined, (input) => `notdefined` ];
212 *
213 * let descriptionOf = pattern([ firstTuple, secondTuple, third, fourth ]);
214 *
215 * console.log(descriptionOf(undefined)); // 'notdefined'
216 * console.log(descriptionOf(55)); // '(55) That's a number!'
217 * console.log(descriptionOf("foo")); // 'Here's your string foo'
218 * ```
219 *
220 * @param struct A 2D array. Each element of the array should be an array, a 2-tuple,
221 * with a Predicate and a mapping/output function
222 * @returns {function(any): *}
223 */
224function pattern(struct) {
225 return function (x) {
226 for (var i = 0; i < struct.length; i++) {
227 if (struct[i][0](x))
228 return struct[i][1](x);
229 }
230 };
231}
232exports.pattern = pattern;
233//# sourceMappingURL=hof.js.map
\No newline at end of file