Since this could lead to ambiguities, we clarify by saying “constructor” in the former case, and “builder” or “builder function” in the latter case.
We define variants (tagged unions), with the following constraints:
A constructor is described by a tag name, followed by zero or more values. Likewise, a tagged structure is described by a tag name, followed by zero or more field names, each field name being mapped to a value.
Two different variants can contain the same constructor or tagged structure, and it is not possible to create an instance of that constructor or tagged structure that would belong to one variant but not the other.
Constructors and tagged structures are "anonymous": it is not necessary to declare a constructor or tagged structure before creating instances of it, expressing its type, or using it as a match pattern.
Constructors types and tagged structures types are "interned": two constructors with the same tag name have the same type, even if they are used in different files. The same applies to two tagged structures with the same tag name and field names: even if they are used in different files, they have the same type.
The datatype package by Andrew Kent also implements Algebraic Data Types. The main differences are that unlike our library, data structures have to be declared before they are used (they are not "anonymous"), and a given constructor name cannot be shared by multiple unions, as can be seen in the example below where the second throws an error:
This library works by remembering all the constructors and all the tagged structures across compilations. More precisely, each constructor’s tag name is written to a file named "adt-pre-declarations.rkt" in the same directory as the user code. The tag name and list of fields of each tagged structure is also written in the same file.
The generated "adt-pre-declarations.rkt" file declares a struct for each tagged structure and constructor, so that all user files which require the same "adt-pre-declarations.rkt" will share the same struct definitions.
User files which make use of the phc-adt should include a call to adt-init before using anything else. The adt-init macro requires the "adt-pre-declarations.rkt" file, and records the lexical context of that require, so that the other macros implemented by this library can fetch the pre-declared struct types from the correct lexical scope. The "ctx.hl.rkt" file takes care of recording that lexical scope, while "adt-init.rkt" performs the initialisation sequence (creating the "adt-pre-declarations.rkt" file if it does not exist, loading the pre-declared struct types from "adt-pre-declarations.rkt", and using a utility from "ctx.hl.rkt" to record the lexical context).
The initialisation process can be somewhat complex: the directives (remember-output-file "adt-pre-declarations.rkt"), () and (require "adt-pre-declarations.rkt") have to be inserted in the right order, and the file "adt-pre-declarations.rkt" has to be initialised with the appropriate contents when it does not exist. The adt-init macro defined in ""adt-init.rkt"" takes care of these steps.
The generated "adt-pre-declarations.rkt" file will call the macro defined in "tagged-structure-low-level.hl.rkt".
We first define a low-level interface for tagged structures in the "tagged-structure-low-level.hl.rkt" file. This low-level interface includes for-syntax functions for expressing the type of tagged structures, creating builder functions for them, as well as match patterns. It also includes means to access the value of a given field on any tagged structure which contains that field. The "tagged.hl.rkt" file provides syntactic sugar for this low-level interface, and defines the tagged identifier, which acts as a type expander, match expander and macro. The macro can be used to create builder functions which return instances of tagged structures, or to directly create such instances.
The ""tagged-supertype.hl.rkt"" file defines a few operations implementing some form of "static duck typing": As a type expander, (tagged-supertype fieldᵢ …) expands to the union type of all tagged structures containing a superset of the given set of fields. As a match expander, (tagged-supertype [fieldᵢ patᵢⱼ …] …) expands to a match pattern which accepts any tagged structure with a superset of the given set of fields, as long as the value of each fieldᵢ matches against all of the corresponding patᵢⱼ ….
We then define untagged structures, which are tagged structures with the untagged tag name. Untagged structures can be used conveniently when the tag name is not important and the goal is simply to map a set of field names to values. The "structure.hl.rkt" file defines the structure type expander, match expander and macro. The structure identifier acts as a simple wrapper around tagged which supplies untagged as the tag name.
Constructors are defined as tagged structures containing a single field, called values. The constructor macro, defined in ""constructor.hl.rkt"" accepts a rich syntax for creating constructor instances containing multiple values, associated with the tag name. The values are aggregated in a list, which is stored within the values field of the tagged structure used to implement the constructor. The constructor identifier is therefore nothing more than syntactic sugar for tagged. It relies on the xlist library, which provides a rich syntax for expressing the complex list types, as well as the corresponding match pattern.
Finally, we directly include the row polymorphism features from "tagged-structure-low-level.hl.rkt":