I need to count matching elements in an array and have two approaches:
// 1 use a reduce to keep from allocating an array
let count = arr -> Array.reduce(0, (count, x) => predicate(x) ? count + 1 : count)
// 2 filter the array, then measure it
let count = arr -> Array.keep(predicate) -> Array.length()
#1 uses a reduce and is a bomb when I need a hammer.
Neither feels as clean as having Array.count(arr, predicate) be a top level function exposed in Belt.
#2 is fine but allocates an array unnecessarily
My question is: is there a reason something as useful as counting is not a first class citizen in StdLib?
To minimize allocations, you can also use a for loop:
let count = (a, f) => {
let result = ref(0)
for i in 0 to Js.Array.length(a) - 1 {
if f(Js.Array.unsafe_get(a, i)) {
result := succ(result.contents)
}
}
result.contents
}
You can use forEach to accomplish the same thing, but I suspect that the loop version would be slightly more performant.
And I don’t have an answer to your question, but it’s possible that just no one has thought to add it yet.
There’s nothing necessarily wrong with using those functions! If you’re using lots of immutable structures then you probably won’t use imperative loops much. Using Belt.Array.forEach / Js.Array.forEach might look slightly nicer since you don’t need to manually keep track of indexes and use unsafe_get each time. But loops are still there for when you want to use them. Belt.Array uses them a lot under the hood IIRC.