Skip to content

Instantly share code, notes, and snippets.

@joshuabowers
Last active January 8, 2024 21:53
Show Gist options
  • Save joshuabowers/522ce0a9a92fccf9cd78b05611ad2f52 to your computer and use it in GitHub Desktop.
Save joshuabowers/522ce0a9a92fccf9cd78b05611ad2f52 to your computer and use it in GitHub Desktop.
typefn: 005 - Higher Order Functions

Functions are treated, in TypeScript, as first-class citizens: they are objects that can be assigned to variables, and as such can be both passed as inputs to and returned as output from other functions, all while still maintaining their ability to be themselves invoked. When treated as an object, a function remains unexecuted until such a time as it has been invoked.

This provides interesting possibilities for a number of different applications. It is likely that, in even just casually using common language features, experience with functions-as-objects has already been had. Consider, for example, array manipulation functions, such as Array.prototype.map, which converts every element of a given array into a new value by applying a mapping function to it. The callback that this function takes in is, itself, a function.

Functions can be returned from other functions. That is, upon invoking a function, the result from that function is another function. This result can be either directly invoked, assigned to a variable for later invocation, passed to another function as input, etc. Furthermore, this function could, itself, generate yet another function as its output. Functions all the way down!

Functions which do either or both of these two things—take another function as parameter or return a function as a result—are described as being "higher order functions", or HOFs. This phraseology comes from mathematics, and comes into functional programming via lambda calculus.

Recalling that each function defines a separate invocation scope which folds in the scoped context in which it was defined, HOFs provide cleaner, safer scope from which to define closures. The parameter list for a HOF, for example, can be utilized within a closure function generated by that HOF; despite the invocation of the HOF being finished, and its corresponding scope removed, some of its data persists within the function it generated.

This can allow for behavioral abstraction, wherein a behavior needed throughout an app can be defined once, parameterized, instanced, and used in multiple areas, all without having to repeat the underlying functional code. This affords both maintainability and encapsulation.

Within TypeScript, a HOF can specify a type definition for a functional input or output inline, or remove it out as a separate type; the latter is not only cleaner, but allows for better self-documentation within the code, and allows other contexts within the code to utilize that type definition. Further exploration of HOFs within TypeScript will be explored in upcoming topics, as their utility is foundational.

// Passing a function as a parameter to another function
const mapped = [1, 2, 3].map( i => i * 10 );
// Define two types: the first describes a function that takes a number
// and returns a boolean---e.g. it assess the truth of its input; the second
// describes a function which takes a value can produces another function
// which might use that value.
type PredicateFn = (n: number) => boolean;
type PredicateHOF = (threshold: number) => PredicateFn;
// A HOF. Note that the return value of this function is a newly generated
// function whenever `makeLessThanPredicate` is invoked; as such, two separate
// invocations of this HOF would have separate values assigned to `threshold`,
// unique to that particular invocation.
const makeLessThanPredicate: PredicateHOF = (threshold) =>
(n) => n < threshold;
// In this context, `isLessThan10` has `PredicateFn` as its type, and it
// encloses the value of `10` in `threshold`.
const isLessThan10 = makeLessThanPredicate(10);
// `isLessThan10` is, itself, a function, to which another value may be
// passed. As it is intended to be a predicate, it can either be directly
// invoked with a value, or itself passed to another function which accepts
// predicates. In the following, `lessThan10` would contain [2, 3, 5, 7].
const lessThan10 = [2, 3, 5, 7, 11, 13, 17, 19].filter(isLessThan10)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment