Functor confusion and "with type" clauses

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)
}
1 Like

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.

Oops can’t use the recommended solution. When I try to use the functor I get an error.

https://rescript-lang.org/try?code=PYBwpgdgBAQmA2AXAUMgtsAJgV3mKiAnuFAFrDAAqx+AvFAN7JQE0HOskCGEAlml3gc8iKLwgALLgCNeiHogDOALgJRaAPihcATjq6EAPD36CNyAL6oMOPJ3wBBPgPjUS9JiyIkULEWMUAER4AczAdYGwVNU0oaQohK3QsXHxvfHJgAFEADxB4YB1wtzpGDnSoAC8KcrYTF2EwUUxQ8MjFJ1N4aOrgdS1dfSN6s0trFLsKgFkuAGswTNz8wuK2egAKDgdVTpcSgBoOUlVMkqgAdzkJe21nQShlegcAOkRDgEp+qHXFvIKinRnS6Ia4VXoPeikV63TA3EbwCFQF6Id7jWz4GbzX7LAGqTELChLf6rdzfLY7O6uGiHFjHMgUIFXOGUxHIj5fTxQfwtCBhCJRXaCRTqb69E6vT6aDgsXoAWg0UPEUlk8ggSnlDj0Bme8zAIHWXC+XA1z14QVa-MUqKSAHobVBKDpCOIQgQ+lE0hJ8DJgAA3MBo1JIykUrpnDy1HwixSIHQuxqiM3BXltKIigD6oliADMhQGkjYg5kTgy1mUvGws1BsHxfMyuiLBfBXgmxJIZHIFML6OnKl8ANoAXTGyXR9OyfxWOhF+OxxJ06yb+3HqKAA

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)