Purpose of genType?

I started using genType with the aim of easing interop with TypeScript. Here’s what I understood about genType’s purpose based on the docs and the GitHub readme:

  • Auto-generate JS-land types from ReScript types
  • Auto-generate ReScript types from JS-land types
  • Auto-generate JS-land bindings for ReScript values
  • Auto-generate ReScript bindings for JS-land values

What I’ve found in practice is that genType can do the following:

  • Auto-generate JS-land types from a subset of ReScript types (for example, no support for dictionaries). This is genuinely awesome and super useful.
  • Stub JS-land types in Reason with an “opaque” type, meaning that the generated type doesn’t retain the data structure of the JS-land type. Not sure what this achieves.

Additionally, it appears that you can import/export values using genType, but I don’t see the advantage of doing it with genType vs. the “normal” way. You still end up writing bindings by hand.

So by my count, genType is 1 for 4 in terms of its usefulness. Its ability to generate opaque types in ReScript doesn’t seem very useful to me and I don’t understand what’s different about genType’s process for importing/exporting values, other than some cosmetic differences (e.g., which decorators you use).

Can someone enlighten me? What am I missing?

As for the overall purpose of the tool, I’ve always viewed it as a means to prevent a lot of work on boilerplate type definitions, not necessarily to “do the work for me.”

Interop with JS/TS will always be a creative effort, and sometimes a struggle, since the type systems are fundamentally different. There is no way around that fact.

As for the “opaque” types that are generated… Sometimes an opaque type is really the best way to write a set of bindings. Some of the data structures we come across in JS-land are truly impossible to represent with ReScript’s primitive types. That’s not to say that you couldn’t write code that “does the same thing” as the JS code… sometimes it’s just hard to write ReScript code that compiles to exactly the same structure as the JS code. Opaque types help a lot with this issue, since they are so flexible.

Perhaps I haven’t worked enough with genType to truly understand the usefulness of opaque types, but I understand your point in the abstract @austindd, it makes sense.

As for exporting values, I realized just now that I’m missing something super obvious, which is that exporting values with genTypes annotates those values, at least if you’re using TS. Vanilla ReScript doesn’t do that. Haven’t tried using genType with JS as the target language, so I’m not sure if there’s any advantage there.

Importing values still doesn’t make much sense to me. I don’t understand what advantage @genType.import has over @bs.module. They seem more or less identical to me.

You need to understand the history of genType first: It was created around 2 years ago, where e.g. records where not compiled to idiomatic JS, and ReasonReact components where not zero-cost (the first version of ReasonReact was built on a record based layer above plain JS).

GenType is also a very important facility for testing out different data structure representations without messing with the compiler workflows. The records-as-objects feature implementation was thoroughly tested with genType (as an external tool), before they got upstreamed in the compiler. The goal of genType is to make itself obsolete at some point, but we are not there yet.

There are still some data structures that need convertion when crossing the boundaries, namely variant types, or other concepts such as labeled arguments (yes, labeled arguments are also not compiled to idiomatic JS as well). GenType makes sure that we have all the necessary converters + type definitions on the TS end.

Importing is a different story, due the fact that we now have a pretty solid poly variant JS representation, records-as-objects, and even easier to express Js.t objects… there’s less need to use genType here.

2 Likes

The difference is that there’s a check that the types match. E.g. if you import a string, with @genType.import and the value is not a string on the JS side, you get a type error (on the JS side: by TypeScript/Flow, whichever one you happen to be using).

6 Likes

Thanks @ryyppy and @cristianoc for your explanations! The rationale behind genType makes much more sense to me now.