Ideas: more attributes to tweak the record layout

Note this is some rough ideas, but would be happy to gauge your interest.

Follow the similar ideas in this thread RFC: More general type checking for structural typings - #51 by Hongbo, I am thinking we can introduce more attributes, it is also something that can only be done in rescript instead of typescript:

@minify
type record1 = {
   field1 : string ,
   field2 : int , 
}

With this attribute minify, all the access of record and creation will be minified based on the type system.

let x = { field1 : "hello", field2: 3 }
x.field1
// generate such code
var x = { a : "hello", b : 3 }
x.a

A similar attribute would be array:

@array
type record1 = {
   field1 : string, 
   field2 : int
}

In this case, record1 will be encoded as an array.

Thoughts?

2 Likes

I’m really happy with the work done with @obj, but I’ll be honest I’m not really sure what the use case for @minify is, except perhaps as a micro-optimisation for reducing overall code size on heavily used record types.

I already have trouble with reading the JS output of code which uses a lot of variants or the pipe operators (like ->) that I’m not sure why I would want something like this. It also seems vulnerable to accidental API breakage when someone puts a field between field1 & field2.

The @array attribute seems like a nice way to get tuple-like output with nominal record types, but I already thought that JS interpreters heavily optimised objects with the same shape for performance.

3 Likes

While we’re on the topic of runtime representation, I think what’s outlined in this issue would be really really valuable for ReScript: [Feature request] zero-cost binding to tagged JS objects · Issue #5207 · rescript-lang/rescript-compiler · GitHub

It’d mean one could seamlessly move between JS/TS and ReScript for variant-heavy code, such as ASTs (GraphQL, Babel, markdown and HTML-parsers etc, even TypeScript itself comes to mind). This in itself is very attractive as one part where ReScript shines is working with AST-like data, thanks to proper pattern matching etc.

@Hongbo, in theory, do you think the work you’ve done with @obj simplifies working on something like what’s outlined in the issue?

7 Likes

Plus one, binding to tagged unions is the one big issue with ReScript in my daily work currently, it makes me constantly wish for typescript in those situations which is a shame. The only ways I have found to safely bind to tagged unions all involve staggering amounts of boilerplate.

4 Likes

+1 for the bindings to tagged unions
I’m using rust + wasm-bindgen + rescript at work and would benefit a lot from this, currently we need to have lots of wrapper functions.

2 Likes

I agree!
+1 for the bindings to tagged unions

1 Like

Can anyone give me an example how bindings of tagged unions would look like or how are useful? I read the linked issue and don’t get it

OK, I re-read the issue and seems that the proposal is to being able to bind to some things that behave like tagged unions on the JS side and let those just be normal tagged unions ing rescript rather than just records. Right?

What are the advantages of this? Seems confusing for no clear benefit

You are encouraged to create a separate topic to discuss the feature you proposed.
Let’s keep the discussions on this topic, otherwise we will get distracted.

Another possibility is introduce a strict attribute, e.g,

@strict
type record = {
   field1 : string,
   field2 : int
}

For records tagged with strict attribute, the compiler will not do anything special with it, this is useful for bindings.
For other records, we can add a command line flag, e.g, --minify-record, so that in production builds, the size gets smaller

3 Likes

I think both way are great.

In the case of a project with low or no interop with js/ts, a command line flag for minify and array with the @strict attribute seems perfect.

However, in the case with high interop, we want most of the record to be “normal”, so adding attribute for @minify and @array seems a better way to go.

Definetly something I want to see in rescript ! :slight_smile:

What do you mean by normal? Normality is a very subjective term. Do you mean always have the same fields?

Regarding @minify:

I’m curious what the use cases for this might be? If code is intended for a browser, then sending the generated code through a minifier library would achieve optimal results, and if the code is intended to run on a server then I’m not sure there is a need for minification?

Regarding @array:

I’m assuming the output would be an array of the values in the same order they appear in the record?

On one hand, this is conceptually similar to tuples, but you can refer to the tuple elements by name which may be nice. E.g. field1 refers to the first element of the “tuple”, and field2 is the second element of the “tuple”.

If I am understanding the proposal correctly then for regular records the order of the fields does not matter, but with the @array decorator the order of the fields does matter. I would have a concern here that it is very simple to change the field order in ReScript and without any warning interop with existing JS may suddenly break. Comparing with tuples we don’t have this same fragility because order is intrinsic to the data structure. Overall @array feels like it may introduce fragile interop behaviour.

As a side note, something like “named tuples” might serve a more intuitive behaviour, such as type record1 = (~field1=string, ~field2=int) and tuple fields could be specified in any order such as let myrecord = (~field2=0, ~field1="x") but the generated code always strictly follows the order of the type definition.

Regarding @strict:

I’m not sure how best to express the concept here, but decorators that have an unambiguous impact on compiler behaviour seems like good language design. Having decorators that may or may not do something depending on command line flags feels like a complicated/confusing developer experience. And if the initial use case is for minification, then I wonder if there is any need. Why not just use a minification library?

Thanks for sharing your thoughts @Hongbo

6 Likes

This is my feeling too

I mean, in an interop context, it’s preferable to have a regular record by default instead of an array representation. By “normal” I mean “regular”, though it was clear… ^^’

That’s why it’s optional and probably should not be used if you need interop on the given structure.

I kinda thought the same. The attribute name mangling will also make the code way harder to read / troubleshoot during runtime. Feels like a lot of extra concepts and I just can’t see any particular use-case where I’d want that builtin minification system over already existing minification infrastructure & gzipping.

Still not sure where this feature would be useful, and I see a lot of footguns whenever a developer adds or reorders a record field.

Another rule to keep in mind. If a library uses it, it’s hard to spot.

For me, the most interesting feature mentioned in this thread would be the (off-topic) zero-cost tagged-union transform.

6 Likes

IIRC, most (all?) JS minifiers don’t minify object field names. I assume this is because doing so would break a lot of JS code that relies on specific object keys.

I’m happy to be proven wrong, though. Or maybe this is a non-issue if you use gzip?

1 Like

I think they only possible compression is through gzip, which should be very efficient in this case.
However the ability to strip out undefined fields is not to save bandwidth, is to ensure correct interaction with Javascript code that doesn’t check the field value and only checks if the field exists at all (even if is undefined). This is more common than you may think.

I also want to re ask what is the use case of the array, other than introducing confusion when you see an array on the generates JS code where it used to be a record.