In your options example, would you be able to create or reference any of the nested types by themselves, or would they always need to be a part of option?
type options = {
persist?: {
fileName: string
}
}
let persist = {
fileName: "foo"
}
let persist = opt => opt.fileName
They’d work by themselves, they’ll be an actual defined type to the compiler. It’s just that (in this proposal as of now) referring to them by typename can’t be done without using escaped identifiers, or by using the @as annotation. Your example above would work just fine because it’d be inferred to be the persist type.
Real types, with autogenerated names that aren’t possible to reference unless using escaped identifiers (a reminder that you should probably break it out to a real type definition if you want to reuse it).
One could also imagining supporting the attribute to set the name for a generated type:@as
I think it’s right that reuse is explicit. What case does the @as addition proposal needed specifically?
It reminds me of the exportFragmentSpreadSubTypes option in the GraphQL code generator. It was quickly abused at scale, so it had to be disabled.
Yeah I lean towards that as well - make it a concrete definition if you need reuse. We can make the tooling make it super easy to extract to a concrete definition.
Definitely not ruling out that, but we should start with records and see how that’s used. Variants are slightly different because of things like @unboxed and @tag where you often want/need to configure the variant at the type definition level. At a glance I think it’s a bit unclear how to achieve that in an intuitive way inline without introducing new syntax. But definitely not out of the question.
I’m a little bit skeptic about using the @as attribute for renaming the underline types.
Because currently it exists to adjust the runtime representation of ReScript data and simply putting the @as to a wrong place will result in a completely different result:
type options = {
startFrom: float,
@as("persistOptions") persist?: { // makes the type `type persistOptions` instead of `type \"options.persist\"
fileName: string,
path?: string,
fileConfig?: {
extension?: string
}
}
}
I’d like to keep @as only for changing the runtime representation.
Would it be possible to have indexed access types to alias nested types directly? They are well proved on typescript nested record access
type options = {
startFrom: float,
persist?: {
fileName: string,
path?: string,
fileConfig?: {
extension?: string
}
}
}
// alias for \"options.persist\"
type persistAlias = options["persist"]
// alias for \"options.persist.fileConfig\"
type fileConfigAlias = options["persist"]["fileConfig"]
Unfortunately I don’t think that functionality from TypeScript translates well to ReScript. The simple cases might (just the equivalent of dot access), but add on things like variants, tuples and more and we’d need likely need to invent more syntax, and do a pretty complicated lookup of types. This lookup would also be alot less useful than in TS. As an example, what if you have a variant where 2 cases has the same prop name, but the props have different types? In TS it’d become typeOfA | typeOfB because that’s how the TS type system works. But in ReScript you can’t mix types like that.
Then there’s the added question around whether to keep things as explicit as possible, which would favor not having this type of lookup. This is also the reason why we’ve rejected @as.
Personally, that functionality in TS has almost always caused more issues than it has solved in codebases I’ve worked in.
Could be explored for sure if someone is interested in taking a stab at investigating how a complete ReScript version of that feature could look/work. But, just like when considering @as, I think we need to keep this type of thing simple to start out.
I’ve been sidetracked with other things, but I look forward to continuing work on this feature soon! Hoping we can land it in v12, that’d be great. Now that I know we can do it I find myself missing this feature almost daily
At this point, what benefit is coming out other than just defining the types?
type fileConfig = {
extension?: string
}
type persist = {
fileName: string,
path?: string,
fileConfig?:
}
type options = {
startFrom: float,
persist?: persist
}
One of the things that draws me to using rescript at work is how straight forward the type system is even if it requires a little extra typing sometimes. I’ve seen some TS codebases that are a nightmare to understand due to getting creative with types
and it seems as though the team is not encouraging reusing types that are defined this way. It should be encouraged that if a type is reused, break it out
The proposal makes perfect sense when I understand records as immutable memory layouts that can no longer be split.
TypeScript’s structs have a quite different effect, since they allow declarations to be reinterpreted based on their structure. That seems pretty detrimental in a nominal language like ReScript.
Polymorphic operations: I’ve argued for deprecating polymorphic operations we have since the introduction of universal operators in v12. For example, we support comparison operations between records. If we were to replace that with codegen based monomorphic operations, nested structures would add some complexity there.
There’s actually a Record&Tuple proposal in progress in JS. Maybe we could experiment with it as an optional target once it’s mature enough. So we need to check in advance that the proposal can interoperate with JS record semantics. I think it’s probably a perfect match, but I’m not 100% sure yet.
Just want to be clear here that this version is “only” syntax sugar - actual record definitions are emitted for each nested record. Although syntax certainly would let us make it nested structures for real later on, it’s not in scope (or evaluated/analyzed) for this first version.