Preface: there are many (many (MANY)) different ways to do codegen. The effects are far-reaching, so they need to be considered carefully; and the cost to support variations is also sky-high, so please be judicious in suggesting "flags" and options.
This is some exploration of the ways we can do builders and constructors for values in the golang code.
(This file does not spend (much) time covering accessors. Accessors are easier.)
(There are also many design choices about where to put the Node and NodeBuilder interfaces; how the type-level representation interacts with the representation-strategy; and how to make all that interface both beautifully and performantly with serialization; etc. All of those have their own complex answers (largely solved), and are not focused here.)
Please also note: This is focusing on the builders and not various internal representations. There is one case where that will get muddled, however: unions. There is more than one clearly valid way to generate union semantics in golang. We'll want to pick one. While it's also possible to have various internal representations, if we start to do that, we will want to select a very small number of these options to actually implement: because we already have the "types"×"representationStrategy" cross-product to support, and adding another dimension for variations in internal code style would make that a triple cross-product. While we can consider that, we need to be incredibly wary of the cost to write and maintain and test and ensure the composability of so much.
Example golang code will be shown.
type Foo struct {
bar Bar
baz Baz
}
type Bar struct {
fwee [String]
}
type Baz int
The '__Content' builders system makes construction of trees of data possible while using familiar golang struct initializing syntax.
Constructing values would look like this:
var foo Foo // type `Foo` is immutable! has unexported fields and accessor methods only
foo = Foo__Content{
Bar: Bar__Content{
Fwee: AnonListString__Content{"a", "b", "c"}.Build(),
}.Build(),
Baz: Baz__Content{3}.Build(),
}.Build()
// n.b. all of these Build methods will panic on any validation errors.
// a closure for capturing those will be provided but there's no other great way to have this compose.
The primary virtue of this approach is that it allows the fields in structs to be clearly seen (this is in contrast to the builder functions approach, discussed more below).
Remarks:
- seems a bit heavy, yes: but buys us:
- immutability all the way through.
- clear places to consistently run any addtnl validation funcs.
- gives room to handle the subtle cases around nullable-and-optional fields (important!).
- can also create values through generic/monomorphized NodeBuilder interface. (it's just uglier.)
- unresolved: are wrappers around basic things (see
Baz
) necessary? perhaps not, but defaulting to 'yes' until proven otherwise. - note that double-underscore is used because we forbid that sequence in type names (thus making us safe from collision problems when generating symbols).
Constructor functions are perhaps the most obvious way to construct trees of data:
var foo Foo // type `Foo` is immutable! has unexported fields and accessor methods only
foo = Foo__Build(
Bar__Build(
AnonListString__Build(
"a", "b", "c",
),
}),
Baz__Build(3),
)
// n.b. all of these *__Build functions will panic on any validation errors.
// a closure for capturing those will be provided but there's no other great way to have this compose.
Remarks:
- heavy in most of the same ways as the '__Content' builders above, but:
- solves all the same problems.
- syntactically a bit lighter-feeling.
- contrasted to the '__Content' builders:
- all args are positional. There's no field names appearing at the usage sites. this may become less clear to read in large structures. (The example here is weak for highlighting this, since the field names and the type names mostly overlap; use your imagination.)
- there may be an alternative to the panicking issue mentioned in the comment...
- it would involve lots of generated
FooOrError
types, though. You can imagine the rest of the details.
- it would involve lots of generated