Being able to describe—and thus, constrain—the type structure of different nodes within an Abstract Syntax Tree is all well and good, but would ultimately be not particularly useful if one had to manually write out the structure of any given node by hand. This would be especially more cumbersome for complicated interactions between nodes, such as when multiple nodes are added together.
This suggests that a set of type-specific functions which generate objects which adhere to the AST node types the functions represent would be ideal. Thus, having functions like add
to create Addition
nodes and numeric
to create Numeric nodes. These functions should take, as parameters, only those details necessary to create their node representations; all other necessary details, such as their Categorization
data, should be automatically added into the objects they generate by the functions themselves.
This, however, would lead to a lot of redundant, repetitious code, where the body of each of these functions replicates much of the same behavior all throughout the code base. Worse, this limits the opportunity to abstract certain behaviors, such as logic unique to common base types. (For example, consider silent type-casting of Bool
instances to Numeric
instances when performing an addition of a boolean and a numeric. Ideally, this wouldn't be behavior that is operation dependent, but could be abstracted out of Binary
contexts.)
As has been showcased in previous articles within this series, such as in the discussion of Generic Tupled Rest Parameters, it is certainly possible to abstract behavior behind additional layers of higher order functions. One should be careful, however: while HOFs are great for making parameterized behavior, too much abstraction can lead to code which is less clear. Complexity for complexity's sake is likely to lead to maintenance nightmares down the line.
In this particular domain, while certain commonality exists between Nullary
, Unary
, and Binary
, they all have downstream unique behaviors which, for the moment, should give a certain degree of pause on leaping immediately to a generic treeNode
HOF which would generate HOFs for each type. Presented in the code for this article is a separate methodology, wherein a treeNode
function exists for standardizing the construction of TreeNode
objects, which is then used by structurally separate HOFs for each of the three direct sub-types.
However, this does lead to an interesting observation: as far as the currently discussed implementation of the spec presented in the overview, there really isn't any direct difference between an Addition
node and a Multiplication
node other than their respective species
sub-field. Those could be safely abstracted behind a HOF. A similar argument exists for Unary
sub-types. While future additions to the system will raise reasonable questions about this current assumption, it is nevertheless a safe direction in which to take the code.
The attached source suggests one approach for describing these interrelated functions, and also provides some sample use of the resulting closures. As should hopefully bb obvious, and building off of the conclusion of the discussion about AST specific type guards, the goal in designing much of the functionality of this domain is easy, clean downstream code.