grafana / grok

Grafana Object Development Kit
Apache License 2.0
50 stars 1 forks source link

Generate non-pointer-laden Go types? #1

Open sdboyer opened 2 years ago

sdboyer commented 2 years ago

Right now, the generated Go types represent optionality in the schema using pointers. So, if we have a CUE/thema schema like this:

Obj: {
    field?: string | *"foo"
}

We'd get the corresponding Go struct:

type Obj struct {
    Field *string `json:"field,omitempty"`
}

This isn't great. It means the most convenient way of writing an Obj instance is:

func Ptr[T any](v T)*T { return &v }

var Obj {
    Field: Ptr("foo")
}

This is awkward. Generics makes this easier, but it's still pretty poor DX compared to just good ol' fashioned struct declarations.

Unfortunately, a nil pointer is the only mechanism Go's type system has for expressing the absence of a field/var. The presence of omitempty doesn't help us - that conflates existence with defaultness, which are things that CUE clearly delineates between. Go, OTOH, just has anemic zero values for defaults.

This results in a pile of ambiguities. With string, we can't tell if the user wants "", or simply didn't specify a value for the field:

These limitations apply whether in the encoded JSON or directly in Go (the former arises from the latter). https://go.dev/play/p/WbDTMNJ_hdx illustrates how only the presence of *string lets us disambiguate.

Still, i wonder if, perhaps in the limited context of use by a builder, we might generate types that don't have all the pointers, just for the sake of DX. Gonna keep this issue open as a reference/to ruminate.

sdboyer commented 2 years ago

Hmm, maybe the best of both worlds would be generating different code if CUE's default is the Go zero value

sdboyer commented 2 years ago

Also, non-pointer types would be much better for the read-time cases, and probably wouldn't have such issues with existence/default ambiguity so long as they don't need to be re-marshaled

DanCech commented 2 years ago

This is one of the most annoying aspects of go for me. Not sure what the best answer is but I would be very hesitant to lose the ability to disambiguate between "not specified" and "set to zero value"

sdboyer commented 2 years ago

Yeah, i quite agree. That's why i ultimately took the pointerful route for initial Go codegen.

i think i may have the pieces of at least a partial solution to it with Thema, but i don't yet see how they fit together into a pattern that i feel is simple enough for everyday use.

Specifically, i have two pieces:

The generic initializer doesn't solve the ambiguity problem in non-pointer types. Nothing can. Still, i have the nagging feeling that there's something just outside my field of view that, combined with these two pieces, lets us significantly relieve the tension between DX and ambiguity.