How it works? (functors and high order types)

I am learning Module | ReScript Language Manual
I can’t understand why with code is not working:

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

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

  let getProfession = (person) => switch(person) {
    | CEO => "CEO"
    | Designer => "Designer"
    | Engineer => "Engineer"
  }
  
};

let mike = Company.CEO
1 Like

See this answer here: Basic Module confusion - #2 by johnj

Module types limit what outside code can “see” in the module. Because EstablishmentType.profession is abstract, code outside of Company isn’t able to see how it’s implemented, and therefore can’t access the CEO constructor.

3 Likes

Thank! Could you explain how can I use it with more complexity types (unioun for exmaple)

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

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

  let getProfession = (person) => switch(person) {
    | CEO => "CEO"
    | Designer => "Designer"
    | Engineer => "Engineer"
  }
  
};

let mike = Company.CEO

I found this way:

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

type profession = CEO | Designer | Engineer

module Company: EstablishmentType with type profession = profession = {
  type profession = profession

  let getProfession = (person) => switch(person) {
    | CEO => "CEO"
    | Designer => "Designer"
    | Engineer => "Engineer"
  }
  
};

let mike = CEO

But it break encapsulation

The answer will probably depend on what you’re trying to do. One possible solution is to define the constructors in the EstablishmentType module type itself:

module type EstablishmentType = {
  type profession = CEO | Designer | Engineer
  let getProfession: profession => string
};

Now they’ll be exposed, but also any module that uses that type will be required to implement the same constructors exactly.

Most of the time, you won’t need to annotate the module itself with the module type. The main benefit of annotating it is to hide the implementation (or debugging), but that isn’t what you want to do here. Modules are structurally typed, so as long as Company has the profession type and getProfession value, then it will be compatible with EstablishmentType. Normally, you will only use the module type in the signature for functors. See this other example from the docs:

module type Comparable = {
  type t
  let equal: (t, t) => bool
}

module MakeSet = (Item: Comparable) => {
  // let's use a list as our naive backing data structure
  type backingType = list<Item.t>
  let empty = list{}
  let add = (currentSet: backingType, newItem: Item.t): backingType =>
    // if item exists
    if List.exists(x => Item.equal(x, newItem), currentSet) {
      currentSet // return the same (immutable) set (a list really)
    } else {
      list{
        newItem,
        ...currentSet // prepend to the set and return it
      }
    }
}

module IntPair = {
  type t = (int, int)
  let equal = ((x1: int, y1: int), (x2, y2)) => x1 == x2 && y1 == y2
  let create = (x, y) => (x, y)
}

/* IntPair abides by the Comparable signature required by MakeSet */
module SetOfIntPairs = MakeSet(IntPair)

Notice that IntPair doesn’t explicitly use the Comparable module type, but it’s still compatible with the MakeSet functor.

4 Likes

Thanks a lot! You really helped me.

1 Like