In Type Guards, two ancillary example functions were presented which purported to simplify the creation of typed, structured objects. (I.e., circle
and square
, for creating, respectively, Circle
and Square
objects.) These objects were previously encoded by hand in Generics, so having functions to automate parts of the process is a step up.
However, consider two points interrelated to the functions provided in Type Guards: first, a degree of repetitious code is shared between the two; second, behavior common to the two that might be capable of being abstracted and encapsulated, wasn't. Now, when dealing with just two very basic functions as presented in those examples, neither of these two points is terribly pressing.
For larger, vastly more complex systems, however, a greater degree of uniformity is certainly desirable. Uniformity, control over behavior, encapsulation of logic and data; as has been previously established in the discussion on Higher Order Functions, HOF's provide an excellent context to afford these desired outcomes. When in need of abstraction, wrap it in another layer of functions.
Circles are noticeably not, importantly, squares. Which suggests that a constructor function for circles might look vaguely similar to the similar function for squares—should one squint—but is unlikely to be exactly like it. What if one were interested in rectilinear boxes instead of squares, where two parameters for width
and height
were used instead of side length? Circles could be abstracted to ellipses, but this sort of generalization isn't always going to scale correctly to encompass all desired types.
One could fashion the closure functions to accept an arbitrary parameter list, using rest params, (e.g.: (...params: any[]) => Circle
, say), but this is unsatisfying: firstly, any number of parameters would be considered by TypeScript to be valid, and secondly, none of the parameters is type safe. Ideally, we would have a fixed number of type strict parameters to force TypeScript to error out on bad uses.
Enter generic tupled rest parameters: these are specialty generic types which, when applied to a rest parameter, cause TypeScript to assign both cardinality and type strictness constraints to an invocations parameter list. As can be see in the first set of examples in this articles source, this allows for an interesting degree of fluidity. To form one of these constructs, have a generic type extend any[]
, then use that as the type of the rest params.
As TypeScript treats tuples as fixed cardinality arrays, and will automatically infer types for generic types in functions where applicable, not specifying an explicit type for the generic will cause a per-invocation inference for the tuple. This is a step in the right direction, but lacks consistency: if any given invocation has variable cardinality, this new admixture solves nothing.
But, what happens when a generic tupled rest parameter is set in the context of a HOF, but used in the context of a closure? Well, for the duration of an invocation, the generic type is realized and fixed, meaning it would be permanent for downstream closures. So, defining such a tuple on a HOF and using on a closure would yield a fixed, type strict parameter list!
As the rest parameters are only used at the point of invoking the closure functions, it becomes necessary to actually bind their generic type to an explicit tuple at the point of creating the closure. This explicitness can be slightly cumbersome, but yields great dividends, as can be seen in the final set of examples provided with this discussion.
Beware of a point of caution: TypeScript will automatically generate names for the parameters based off of whatever name is used for the rest parameter. In VS Code, for example, IntelliSense will flag these parameter names whenever highlighting a function with the cursor or upon writing an invocation. Other tooling is likely to behave similarly. For the examples provided here, these names are generated as params_0
, params_1
, etc. This naming behavior is fixed an not readily alterable. As a consequence, invocations of closure functions will be slightly more esoteric, contextually, than a hand-crafted function might be.
As a final note: generic tupled rest parameters are restricted to neither basic system literal types, nor to a single uniform type for the whole list. It is possible to use these to bind to any user-defined type of arbitrary complexity, as well as to bind any arbitrary set of differently typed parameters in fixed positions.
Generic tupled rest parameters provide an interesting tool for injecting cardinality and type strictness into situations where situational flexibility is required. Consider their use when looking for abstracted encapsulations in the future.