Your code looks quite reasonable, though there are a few tweaks you could consider.
Function or Callback? Rather than names like FoldlFunction
and MMapFunction
, since what those types contain are the types for the callback run for each element of the array, consider calling them FoldlCallback
etc instead. It's a bit more precise and will make more intuitive sense at a glance. (One might think at a glance that FoldlFunction
might refer to the type of the whole Foldl
function, which is incorrect.)
camelCase
is the near-universal convention for plain non-class functions and variables in JavaScript. PascalCase is generally used for class constructors and namespaces, which aren't being used here, so camelCase is probably more appropriate. Eg Foldl
-> foldl
(or to foldL
).
DRYer types You only need to specify the type of a parameter when TypeScript can't infer it automatically. For example, with foldl
, since the base value is T, and the array is T[]
, the callback - the f: FoldlFunction<T>
- will be known to be of the form (a: T, b: T) => T
. There's no need to additionally specify the arguments' types when calling foldl
. That is, this:
foldl((a: number, b: number) => (a + b), 0, [1,2,3,4,12,14,15,16])
can be
foldl((a, b) => (a + b), 0, [1,2,3,4,12,14,15,16])
It's a good idea to let TypeScript automatically infer the types of variables and parameters whenever possible - less code to read makes comprehension easier. You can do the same thing for MMap
and Filter
.
Outside definition? I'm not entirely convinced of defining the type of the function/callback outside - it's one more type parameter that needs to be cognitively pieced together. Consider defining the callback type in the parameter list:
function foldl<T>(
f: (a: T, b: T) => T,
base: T,
arr: T[]
): T {
const [head, ...rest] = arr;
return !head ? base : foldl(f, f(base, head), rest);
}
console.log(foldl((a, b) => (a + b), 0, [1,2,3,4,12,14,15,16]));
I consider that significantly more readable. You can do the same thing for MMap
and Filter
.
Falsey bug You use return !head ? base : foldl...
. This will not run if head
is falsey. For example, foldl((a, b) => (a + b), 0, [1,2,3,4,12,14,15,16])
results in 67, but foldl((a, b) => (a + b), 0, [0, 1,2,3,4,12,14,15,16])
results in 0, which probably isn't desirable. Check the array's length instead:
function foldl<T>(
f: (a: T, b: T) => T,
base: T,
arr: T[]
): T {
if (!arr.length) {
return base;
}
const [head, ...rest] = arr;
return foldl(f, f(base, head), rest);
}
This applies to MMap
and Filter
as well.
DRYer filter To fix the falsey bug and make Filter a bit cleaner, you can replace:
const [head, ...rest] = arr;
return !head ? [] :
(f(head) ? [head, ...Filter(f, rest)] : [...Filter(f, rest)])
with
if (!arr.length) {
return [];
}
const includeThis = f(head); // call this here to preserve callback execution order
const restFiltered = Filter(f, rest);
return includeThis ? [head, ...restFiltered] : restFiltered;