Small exercise for understanding modules

As a small ReScript learning exercise I’m using modules to model abstract algebraic semigroups. So far I have:

// A module interface for abstract semigroups:
module type Semigroup = {
  type t

  let op: (t, t) => t
}

// module interfaces can't have default functions so the
// semigroup laws need to be in a separate module:
module SemigroupLaws = (S : Semigroup) => {

  let associative = (x: S.t, y: S.t, z: S.t) =>
    S.op(x, S.op(y, z)) == S.op(S.op(x, y), z)
}

// Integers form a semigroup under addition (ignoring overflow):
module IntSemigroup: Semigroup with type t = int = {
  type t = int

  let op = (x: int, y: int) => x + y
}

// Integer semigroup addition as expected:
Console.log(IntSemigroup.op(2, 3))

But I think I’m missing or misunderstanding a technique to create an int instance of SemigroupLaws. Is this a use case for module functors?

module IntSemigroupLaws: SemigroupLaws(???)

Any advice much appreciated.

1 Like

You already created a module functor in your example, i.e. SemigroupLaws. So you just need to apply it to get the instance you want. Functor application looks exactly like function application:

module IntSemigroupLaws = SemigroupLaws(IntSemigroup)

Btw, you can simplify a few things in your code thanks to ReScript type inference and operators being first-class values:

module SemigroupLaws = (S : Semigroup) => {
  let associative = (x, y, z) =>
    S.op(x, S.op(y, z)) == S.op(S.op(x, y), z)
}

// Integers form a semigroup under addition (ignoring overflow):
module IntSemigroup = {
  type t = int
  let op = \"+"
}
3 Likes

That’s very neat. Thanks you for those insights!