You could define a wrapper type which satisfies the requirements of Monoid. You could then simply use R.concat
to combine values of the type:
// Thing :: { id :: String, failedReason :: Array String } -> Thing
function Thing(record) {
if (!(this instanceof Thing)) return new Thing(record);
this.value = {id: record.id, failedReason: R.uniq(record.failedReason)};
}
// Thing.id :: Thing -> String
Thing.id = function(thing) {
return thing.value.id;
};
// Thing.failedReason :: Thing -> Array String
Thing.failedReason = function(thing) {
return thing.value.failedReason;
};
// Thing.empty :: () -> Thing
Thing.empty = function() {
return Thing({id: '', failedReason: []});
};
// Thing#concat :: Thing ~> Thing -> Thing
Thing.prototype.concat = function(other) {
return Thing({
id: Thing.id(this) || Thing.id(other),
failedReason: R.concat(Thing.failedReason(this), Thing.failedReason(other))
});
};
// f :: Array { id :: String, failedReason :: Array String }
// -> Array { id :: String, failedReason :: Array String }
var f =
R.pipe(R.map(Thing),
R.groupBy(Thing.id),
R.map(R.reduce(R.concat, Thing.empty())),
R.map(R.prop('value')),
R.values);
f([
{id: '001', failedReason: [1000]},
{id: '001', failedReason: [1001]},
{id: '001', failedReason: [1002]},
{id: '001', failedReason: [1000]},
{id: '001', failedReason: [1000, 1003]},
{id: '002', failedReason: [1000]}
]);
// => [{"id": "001", "failedReason": [1000, 1001, 1002, 1003]},
// {"id": "002", "failedReason": [1000]}]
I'm sure you could give the type a better name than Thing. ;)