The crown jewels of JavaScript for Automation (JS generally, in fact) are the ‘higher-order’ Array functions, particularly Array.map() and Array.reduce()
Map returns a transformed version of an array (applying a supplied function to each of its members)
Reduce (often called fold or foldl, foldLeft etc in other languages) takes:
- an array, and
- an initial seed or ‘accumulator’ value,
and works through each value in the Array, using a supplied function to update the accumulator value, and then returns a final value.
-
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
-
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
A surprising number of problems can be solved by working through arrays of values with map and reduce/fold, but they do have one limitation - they only work with simple flat arrays. They don’t work with nested arrays, dictionaries of key:value pairs, or combinations of these two.
Here, for the snippet library of anyone using Execute JavaScript for Automation actions, or Execute JavaScript in (Safari|Chrome) actions, are slightly more general versions of map and reduce: fmap and fold
fmap(Function, JSValue)
can produce exactly the same result as Array.map(), if its second argument is a flat Array, but it can also map a single argument function over values in arbitrarily nested Arrays and/or Key:Value objects.
fold(Function, StartValue, JSValue)
can produce exactly the same result as Array.reduce(), if its second argument is a flat Array, but it can also progressively update an accumulator by working a two argument function (accumulator, value) through all the values in arbitrarily nested Arrays and/or Key:Value objects.
Code with examples of use:
(function () {
'use strict';
// A MORE GENERAL map -- NOT JUST FOR FLAT ARRAYS
// fmap :: Function -> JavaScript Object or Atom -> transformed ditto
// fmap :: (a -> b) -> f a -> f b
function fmap(f, jso) {
var t = typeof jso;
if ("undefined" !== t) {
if ("object" === t) {
if (null === jso) return null;
// Array
if (jso instanceof Array) {
return jso.map(function (v) {
return fmap(f, v);
});
}
// Dictionary
var fb = {};
return Object.keys(jso)
.forEach(function (k) {
return fb[k] = fmap(f, jso[k]);
}), fb;
// Atomic value
} else return f(jso);
}
}
// A MORE GENERAL fold / reduce -- NOT JUST FOR FLAT ARRAYS
// fold :: Function -> Start Value ->JavaScript Object or Atom -> transformed ditto
// fold :: (a -> b -> a) -> a -> c -> a
function fold(f, v, jso) {
var t = typeof jso;
if ("undefined" !== t) {
if ("object" === t) {
if (null === jso) return v;
// Array or dictionary
var lst = jso instanceof Array ? (
jso
) : Object.keys(jso)
.map(function (k) {
return jso[k];
}),
a = v;
return (
lst.forEach(function (x) {
a = fold(f, a, x);
}),
a
);
// Atomic: String | Number | Bool
} else return f(v, jso);
}
return v;
}
/******* EXAMPLES ******/
var doubleNumber = function (x) {
return typeof x === 'number' ? x * 2 : x;
}
var sumNumbers = function (a, b) {
return (!isNaN(a) && !isNaN(b)) ? a + b : a;
}
var fmapExamples = {
fmapSimpleArray: fmap(doubleNumber, [1, 2, 3, 4, 5]),
fmapNestedArray: fmap(
doubleNumber, [1, 2, 3, [4, 5, 6], [], [[7, 8, 9]]]
),
fmapDictionary: fmap(doubleNumber, {
alpha: 10,
beta: 20,
gamma: 30
}),
fmapMixed: fmap(doubleNumber, [{
alpha: 40,
beta: 50
}, {
gamma: 60,
delta: [64, 128]
}])
},
foldExamples = {
foldSimpleArray: fold(sumNumbers, 0, [1, 2, 3, 4, 5]),
foldNestedArray: fold(
sumNumbers,
0, [1, 2, 3, [4, 5, 6], [], [[7, 8, 9]]]
),
foldDictionary: fold(sumNumbers, 0, {
alpha: 10,
beta: 20,
gamma: 30
}),
foldMixed: fold(sumNumbers, 0, [{
alpha: 40,
beta: 50
}, {
gamma: 60,
delta: [64, 128]
}])
}
return [fmapExamples, foldExamples];
})();