GenType not working when functor return type is extracted

I’m finding it unpredictable/flaky working with GenType and Functors and am not sure what rules to follow to guarantee success. No matter how I do it, the code compiles and works inside ReScript. But when I try to work with my types from Typescript, sometimes the .gen.tsx file won’t load in the browser. In this particular case, if the return type of the functor is defined as a named type and then referenced in the functor, the code fails. But if the functor return type is defined inline, it works. Why? I have a small GitHub project where you can see the compiled .gen.tsx and unit test results: https://github.com/jmagaram/functors.

This is broken…

module type Config = {
  type domain
  let validate: domain => option<domain>
}

module type T = {
  type t
  type domain
  @genType
  let make: domain => option<t>
  @genType
  let value: t => domain
}

module Make = (C: Config): (T with type domain := C.domain) => {
  type t = C.domain
  let make = C.validate
  let value = v => v
}

But this works…

module type Config = {
  type domain
  let validate: domain => option<domain>
}

module Make = (C: Config): {
  type t
  @genType
  let make: C.domain => option<t>
  @genType
  let value: t => C.domain
} => {
  type t = C.domain
  let make = C.validate
  let value = v => v
}
1 Like

Simplest fix is to move this:

module type Config = {
  type domain
  let validate: domain => option<domain>
}

module type T = {
  type t
  type domain
  @genType
  let make: domain => option<t>
  @genType
  let value: t => domain
}

module Make = (C: Config): (T with type domain := C.domain) => {
  type t = C.domain
  let make = C.validate
  let value = v => v
}

into it’s own module/file…eg., PickAGoodName.res. Then PositiveExtracted.res only has

@genType
module PositiveInt = PickAGoodName.Make({
  type domain = int
  let validate = n =>
    if n > 0 {
      Some(n)
    } else {
      None
    }
})

Then, check the gen.tsx output is the same.

diff src/PositiveInlined.gen.tsx <(sed 's/Extracted/Inlined/g' src/PositiveExtracted.gen.tsx)

I ran the tests and they’re fine as well.

By the way, it would probably be a good idea to link to your other two questions here as well since they’re all related. Would be helpful for future readers I think.

Yes your recommended way of coding this works and is the way I actually do it in my real code. So there is a base module/file called something like NewType or ValidatedPrimitive with the functor. And then each derived type is in its own file like PositiveInteger or UniqueId. But do you know why the .gen.tsx does NOT work when everything is put in the same module? It seems like the results should be the same. From the ReScript side it works either way. I updated my project and tests: https://github.com/jmagaram/functors

By the way this helps explain some of the flakiness I’m seeing. For experiment purposes, I put everything into nested modules in a single file. And it breaks. But then when I go back to my real code - where I have factored it out into separate files - it usually works.

This issue is a part of How to create distinct uuid types AND access them from TypeScript

1 Like

I can only guess as I haven’t really dived in to the genType codebase…but I think it might just not know how to deal with module types and functors…If you look at the generated output, it looks like it’s trying to treat your module type T in a similar way to the module PositiveInt:

(lightly modified for clarity)

export abstract class T_t { protected opaque!: any }; /* simulate opaque types */
export abstract class PositiveInt_t { protected opaque!: any }; /* simulate opaque types */

export const T_make: (_1:T_domain) => (null | undefined | T_t) = PositiveExtractedBS.T.make;
export const PositiveInt_make: (_1:number) => (null | undefined | PositiveInt_t) = PositiveExtractedBS.PositiveInt.make;

export const T_value: (_1:T_t) => T_domain = PositiveExtractedBS.T.value;
export const PositiveInt_value: (_1:PositiveInt_t) => number = PositiveExtractedBS.PositiveInt.value;

But of course, module types are not modules. And so if you go to the generated JS, there is no T object to “hold” the functions (there shouldn’t be…it’s just a module type after all.)

So since it doesn’t seem to work quite right, I just use the “hack” of sticking some the stuff that needs genType (but that genType doesn’t handle quite correctly) in it’s own file. Which some? Well, if genType is used within a module type or a functor. Because neither of those is “used” in the file where they’re defined, the genType won’t generate types for that file, and then you won’t get that messed up thing where it is trying to treat the module type like a module.

Of course, I don’t know the true reason for all of this as I haven’t dived in to the code, but those are my observations.

Anyway, just more examples of the module language being kind of “different” from the rest of the language and needed special care to use properly.