For any sufficiently complicated system involving interrelated types, it will eventually become desirable to be able to ascertain the precise type of a given configuration of data; that is, more exactly, given data, figure out whether it adheres to a given specification.
In the discussion about Type Guards, methodologies for ascertaining, and seamlessly casting to, types was presented. Could such behavior be adapted to the context of the type system which was previously refined for the abstract syntax tree? Certainly!
Type guards, as a refresher, are predicates which, as a secondary effect of evaluating a relative truth about a passed value, also assert that the passed value is or is not of a given type. This latter functionality is purely behavior which exists only within the context of a TypeScript runtime; it is not transpiled to JavaScript, so never sees presence within a browser or server environment. Any given type guard can use any sort of predicate to ascertain what sort of data it is operating on; as far as failure conditions go, any sort of false result is a negative assertion about type, and conversely with respect to truth.
Knowing the application domain is key: should one be suspicious about the validity of the data that is being evaluated—the auspices under which it was generated, or whether one might trust the context from which it originated—a degree of rigorous and fastidious hyper-specificity would be called for. On the other hand, should the data only ever be created from well defined, well known, well trusted sources, a degree of laxity might be allowable. As always, robust testing of a system, as well as vigilance with respect to a live system, is necessary.
For the AST being developed in this system, it will be sufficient to simply test various sub-fields within a given TreeNode's about
field: a given species
is unique to a given type, thus not reused or re-contextualized; meanwhile, while an arity
is shared between types, a given arity always comports with a given data structure. It would be relatively safe to assume these invariants, and simplify tests accordingly. The code presented with this discussion makes this assumption in developing its type guards.
(Should, however, one be overly paranoid, the code also presents an alternative approach which more exactingly evaluates a given object to ensure it contains specific fields. This alternative is isomorphic with the simpler approach in terms of the generated guard function type, and so would be interchangeable in future code.)
As a continuing goal of this series, derivative code and consuming code well downstream should be simple to write, maintain, use, and reason about. Many of these aspects can be accomplished via sufficient abstraction. Through use of HOFs, generics, and closures, as well as knowing system invariants, it is possible to write clean, tidy, minimalistic code.