Similiar Shaped Types

Is there a better way to handle types that are shaped the same?

When making a lot of types in the same module, I’ve hit a few occasions, both in ReScript and TypeScript, where it gets confused between the 2 types because they have the exact same shape. I end up adding types on the functions, and sometimes helper functions to help the compiler out. Today I had 3 of them that were just { name: string } and got so frustrated, I just went { name1: string } { name2: string } and { name3: string } and had Jzon figure out the mess.

let getThing = (thing:string):another = { name: name }

The issue is some of those helper functions make it into the compiled JavaScript, and coverage sees them as not used and not covered (yes, I’m trying to fix the coverage problem in another thread).

Example:

type something = {
  body: string
}
type another = {
  body: string
}

Especially when I bring Jzon encoding/decoding into the picture, the above types confuse the compiler, and sometimes it’s like “Hey, you’re using a something here, but it should be an another.” and I’m like “Naw, ReScript, that’s just because they look the same…”

Yeah, standard way is to put them in their own modules:

module Something = {
  type t = {body: string}
}

module Another = {
  type t = {body: string}
}

Now you can refer to them unambiguously using a module prefix:

let getThing = thing => {Another.body: thing}
let sth = {Something.body: "something"}
let {Something.body} = sth 

Of course, now that you have these submodules, they are also an obvious place to put the decoders:

module Something = {
  type t = {body: string}

  let decode = json => ...
}
3 Likes

That’s not confusion, it’s shadowing. Defining a record creates scoped names for each of the fields; when there are overlapping field names only the latest can ever be in scope. Something is required to disambiguate them in this scenario.

Sometimes the compiler is able to use type inference to disambiguate, which is why you don’t always see the error, but when it can’t infer the type the scoped field names are used. There are two ways to manually disambiguate which record type was intended:

  • ensure each record has at least one unique field name, and use it first when creating any instances of the record
  • @yawaramin’s suggestion of nested modules
1 Like

Thanks y’all! I just assumed shadowing wasn’t a thing in types and records were unique, DOH!

Ok, I’ll have to practice this module thing as the separate name stuff is killing my domain design drive.

1 Like

Can you show a full example for me? I dont see where the problem is.
records are nominatively typed and so you should be able to assert that a function/value take one type or the other with annotation whenever you want?

Thanks
A

yes function calls are one example where the compiler can infer which fields to use. But it can’t for standalone let variables without annotations, which can make this quite a complicated scenario to develop in. This isn’t a language where explicit type annotations should be expected (other than in interface files).

Here’s an example that demonstrates function disambiguation, and also the unique field option.
https://rescript-lang.org/try?code=C4TwDgpgBAzg9gWwsAFgSwHYHMoF4oDeAUFFAGZoQA2AJgFyzABOmWRAvkVclMAIYBrCDADKiZOmx4oAChgBKPAD5CJKACkYAOipwscrRWo0oAalNQARFADufGFD6MW2S-I5EioSI4xxUEEzSxKRGtAyYwB7cwLyCwgCCfgFB+DJ8irgqIRrauvp8hpS0ZlAAjO6cXuDQKPYAqhhoAI4ArhAAYsUm+Dlh9OS6fMAANGoYfEgMMMysHkQA9AtQEAAek2DcUHBk5K0YAMbAaHAYUAd8VFQOkExkcEwIrLw1UDRoMJMARmhYrcMnDBeeKicSoVgyAj9BiWFDUXSWdjufhCGBJfxwpiQ6HlJGeJYrdYITbQHZQfYtdrkboOGhwZ4BWCTCBcHh1GCNSkQAAqr16ahxZS0AAYxqQJlMrOCHDEHIyDogwGhuEF3p8ED8-sNoDZ0AcUC8fMA4OSYBBLPMCV9WrEbGhUHAbb4QL5kgDTiNthgqC7GQg4DMoEwIAcIBhYsGFUwTN5SbtpbA6j4LmcvtBMGRAsGaKzYjM+BgaJdTtB+aFujCU1BklA045nM87Q6nWtNmgDvafbBIB2yCAGXCoAADeBIcHYIeG80cIA