You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Our current approach to generating Typescript defaults is to create a typed constant, and leave it to the caller to figure out how to conditionally apply that constant to the type in order to replace absent values with the CUE-specified default. This approach has two, not-unrelated problems:
Leaving the mechanism for default application to the user leaves room for user error
It is impossible (barring some TS type system magic i'm unable to imagine) to express defaults in a const for values within optional nested structures
The latter case could be considered a bug, as it violates cuetsy's goal of having the same semantics in Typescript as in CUE. Specifically, this is the problem:
We have two basic choices - either we generate a default const that contains inner, or we don't:
exportinterfaceOuter{top: stringinner?: {value: string}}// Option 1 - exclude the inner structureexportconstouterDefault: Outer={top: "foo"}// Option 2 - include the inner structureexportconstouterDefault: Outer={top: "foo"inner: {value: "bar"}}
Both of these options are critically flawed. Option 1 simply omits default information entirely, flagrantly violating any notion of semantic equivalence. Option 2 unconditionally includes the nested inner value, which effectively conflates the absence of inner with it being present for any object to which this default is applied.
This is especially problematic if inner contains another field that is required and lacks a default, like inner.req. outerDefault becomes completely impossible to use just for its outermost field default (top) due to a property of one of its optional nested structures.
Solution?
i don't know exactly what the best solution here looks like, but i'm reasonably sure that it will entail switching to functions for applying defaults, as it's the only way to achieve the necessary conditionality. At least, i think it's the only way - perhaps some type magic would allow us to get around it. However, given that we also have the first problem - leaving application a problem for the user - it seems preferable to just embrace functions, as it allows us to target a single pattern for all defaults: a function that returns T, with a parameter list that is basically a Pick<T, (the list of required fields that lack a default)>. Each level of structure would probably end up getting its own function, at which point...idk, something like uncurrying to roll everything up to the parent? I haven't thought it fully through.
i think we can do something less extreme for lists, at least for now.
For starters - and going back on something i'd said earlier - i think it makes the most sense to treat empty lists and structs as semantically equivalent to absent ones. This makes me itch a bit, as we don't do the same with scalar values.
I can't put words to a precise explanation of why we should treat them differently. But lists and structs are grouping constructs. Ultimately, it just seems more right to acknowledge this by treating them differently in the context of absence/default questions than the various scalar types.
Problem
Our current approach to generating Typescript defaults is to create a typed constant, and leave it to the caller to figure out how to conditionally apply that constant to the type in order to replace absent values with the CUE-specified default. This approach has two, not-unrelated problems:
The latter case could be considered a bug, as it violates cuetsy's goal of having the same semantics in Typescript as in CUE. Specifically, this is the problem:
We have two basic choices - either we generate a default const that contains
inner
, or we don't:Both of these options are critically flawed. Option 1 simply omits default information entirely, flagrantly violating any notion of semantic equivalence. Option 2 unconditionally includes the nested
inner
value, which effectively conflates the absence ofinner
with it being present for any object to which this default is applied.This is especially problematic if
inner
contains another field that is required and lacks a default, likeinner.req
.outerDefault
becomes completely impossible to use just for its outermost field default (top
) due to a property of one of its optional nested structures.Solution?
i don't know exactly what the best solution here looks like, but i'm reasonably sure that it will entail switching to functions for applying defaults, as it's the only way to achieve the necessary conditionality. At least, i think it's the only way - perhaps some type magic would allow us to get around it. However, given that we also have the first problem - leaving application a problem for the user - it seems preferable to just embrace functions, as it allows us to target a single pattern for all defaults: a function that returns
T
, with a parameter list that is basically aPick<T, (the list of required fields that lack a default)>
. Each level of structure would probably end up getting its own function, at which point...idk, something like uncurrying to roll everything up to the parent? I haven't thought it fully through.cc @ryantxu
The text was updated successfully, but these errors were encountered: