Implement .resi in .res file

Hi, I’m new to ReScript, I have a .resi file that contains the following module signature:

// Id.resi
module type Id = {
  type t
  let fromString: (~s: string) => t
  let toString: (~id: t) => string
  let generate: () => t
}

I want to implement the signature above in .res file but I don’t find how to do it in the docs, I tried the following without success:

// UUID.res
module UUID: Id = {
  type t = string
  let fromString = (~s: string) => s
  let generate = () => fromString(~s=Uuid.V4.make())
  let toString = (~id: t) => id 
}

Any help is appreciated, thank you in advance.

Can you post the exact error message you get, please?

I can’t replicate an error with your given code.

1 Like

Here is the error:

 1 │ module UUID: Id = {
  2 │   type t = string
  3 │   let fromString = (~s: string) => s

  Unbound module type Id

I have the definition and the implementation in separate files.

Ah, now I see the misconception:

resi files define the public interface for the res file of the same name.

Fore example:

// A.resi
type x
let b: string
// A.res
type x = string
let a = "!"
let b = "Hi"++a

A.resi defines the “shape” of the module A.res, how it is perceived from the outside: There is a type called x, which we don’t know the actual implementation of and there is a string value named b. Because of the interface file, the value a is not accessible from other modules.
It’s somehow similar to node’s module exports.

On the other hand, the module A.res must implement everything, which is stated in A.resi!
module type is a “first class citizen” just like type.

In your case you specify in the interface file Shared.resi, that the module Shared.res contains a module type Id of the given shape.
But your actual implementation defines a module called UUID and uses the module type Id, which is not specified yet in the implementation.

So, you either want this:

// Shared.resi
module UUID: {
  type t
  let fromString: (~s: string) => t
  let toString: (~id: t) => string
  let generate: () => t
  }
// Shared.res
module UUID = {
  type t = string
  let fromString = (~s) => s
  let generate = () => fromString(~s=Uuid.V4.make())
  let toString = (~id) => id 
}

Or you define the module type in your implementation:

// Shared.resi
module type Id = {
  type t
  let fromString: (~s: string) => t
  let toString: (~id: t) => string
  let generate: () => t
  }

module UUID: Id
// Shared.res
module type Id = {
  type t
  let fromString: (~s: string) => t
  let toString: (~id: t) => string
  let generate: () => t
  }

module UUID = {
  type t = string
  let fromString = (~s) => s
  let generate = () => fromString(~s=Uuid.V4.make())
  let toString = (~id) => id 
}

Defining a module type Id makes sense, if you plan on having several modules implementing this interface.

Edit: Is there a text or wording in the docs, which you feel might have mislead you? Or should be improved for better understanding?

3 Likes

Thank you for the clear answer, I appreciate it.

I think the docs should mention that the signature must be in the same file (root module) of the implementation to work as expected, I suggest that module Company to be added to the signature example above it, so the implementation example would be:

module type EstablishmentType = {
  type profession
  let getProfession: profession => string
}

module Company: EstablishmentType = {
  type profession = CEO | Designer | Engineer | ...

  let getProfession = (person) => ...
  let person1 = ...
  let person2 = ...
}

Instead of just:

module Company: EstablishmentType = {
  type profession = CEO | Designer | Engineer | ...

  let getProfession = (person) => ...
  let person1 = ...
  let person2 = ...
}