I’m trying to use Functors to create a module but can’t figure out how to “join” two types/modules - the Zoo module and the Animal module. I’ve tried various forms of the “with type …” but nothing compiles. How do I make the “ZooExplorer”?
module type ZooType = {
type t
type animal
let inhabitants: t => array<animal>
}
module type AnimalType = {
type t
let isDangerous: t => bool
}
module type ZooExplorerType = {
type zoo
type animal
let dangerousAnimals: zoo => array<animal>
}
module type MakeZooExplorerType = (A: AnimalType, Z: ZooType) =>
(ZooExplorerType with type zoo := Z.t and type animal := A.t)
module MakeZooExplorer: MakeZooExplorerType = (A: AnimalType, Z: ZooType) => {
// Error on 'a' in keep clause
// This has type Z.animal; somewhere wanted A.t
let dangerousAnimals = (zoo: Z.t) => zoo->Z.inhabitants->Array.keep(a => a->A.isDangerous)
}
As per the code the animal type in ZooType is different from A.t. Thats the reason for the error. The below code compiles fine.
module type ZooType = {
type t
type animal
let inhabitants: t => array<animal>
}
module type AnimalType = {
type t
let isDangerous: t => bool
}
module type ZooExplorerType = {
type zoo
type animal
let dangerousAnimals: zoo => array<animal>
}
module type MakeZooExplorerType = (A: AnimalType, Z: ZooType with type animal := A.t) =>
(ZooExplorerType with type zoo := Z.t and type animal := A.t)
module MakeZooExplorer: MakeZooExplorerType = (
A: AnimalType,
Z: ZooType with type animal := A.t,
) => {
let dangerousAnimals = (zoo: Z.t) => zoo->Z.inhabitants->Belt.Array.keep(a => a->A.isDangerous)
}
I don’t know the exact reason for the design, but why not simplify the code by using union types like below.
module Animal = {
type t = Lion | Tiger
let isAnimalDangerous = a =>
switch a {
| Lion => true
| Tiger => true
}
}
module type ZooType = {
type t
let inhabitants: t => array<Animal.t>
}
module type ZooExplorerType = {
type zoo
let dangerousAnimals: zoo => array<Animal.t>
}
module type MakeZooExplorerType = (Z: ZooType) => (ZooExplorerType with type zoo := Z.t)
module MakeZooExplorer: MakeZooExplorerType = (Z: ZooType) => {
let dangerousAnimals = (zoo: Z.t) => zoo->Z.inhabitants->Belt.Array.keep(Animal.isAnimalDangerous)
}
Doing some of this recently I noticed that there is some ordering requirements on type association in functor arguments which was a little surprising to me. So it becomes a directed association which can get interesting if you have two or three module types youre trying to tie together.
Thanks. Very helpful. Of course my application doesn’t have to do with animals or zoos. I was trying to make a simplified example. Right now I’m finding it easiest to model my problem domain as separate independent modules and then stitch them together using Functors; hope this ends up working for me as my application grows.
Annotating the Zoo with ZooType like below should work.
// Trying to use the above
module Animal: AnimalType = {
type t = string
let isDangerous = _t => false
}
module Zoo: ZooType with type animal := Animal.t = {
type t = unit
let inhabitants = _z => []
}
module ZooExplorer = MakeZooExplorer(Animal, Zoo)