Equivalent of a 'method in an abstract class' - or alternative?

Hi Everyone,

I’m trying to write some code for doing abstract algebra. I know how I would write the code in an object-oriented language (I have some of it written in Typescript), but I don’t know what the equivalent would be in ReScript. For example, I am trying to have some kind of ‘Group’ structure with functions for acting on elements of a particular type. All that is required is an identity element and multiply and inverse functions, and the rest can be defined from that. An example might look as follows:

module type Group = {
  type el
  let identity: el
  let multiply: (el, el) => el
  let inverse: (el) => el
  let isEqual: (el, el) => bool
}

module MakeGroup = (Group: Group) => {
  include Group

  let divide = (a, b) => multiply(a, inverse(b))
  let isIdentity = (e: el) => isEqual(e, identity)
}

module AdditiveIntGroup = {
  include MakeGroup({
    type el = int
    let identity = 0
    let multiply = (a, b) => a + b
    let inverse = e => -e
    let isEqual = (a, b) => a == b
  })

  let divide = (a, b) => a-b
}

This seems like it would work, but is this idiomatic? It doesn’t seem to provide the best compiled Javascript code (a lot of essentially duplicated object definitions). Is there a different way I should be thinking about this?

Check out https://github.com/Risto-Stevcev/bastet which is a fairly full-fledged library for abstract algebra. You may be able to just use that or at least get some ideas from there.

Thanks for the link. It does help a little. It is interesting to see that they implement the module for the group of integers as a submodule of Int.

It looks like the library you linked doesn’t provide extra functions in the modules, outside of the ones strictly necessary for defining the module. For example, I want to have a function pow: (el, int) => el that raises an element to an integer power. This function can immediately be defined once the basic operations are provided, so it should not be required to be defined explicitly for every module implementing Group.

I just realised that my above approach doesn’t work. If there is another function foo in MakeGroup that uses isIdentity, for example, and I override isIdentity in a new module, then foo defined in the new module will use the old isIdentity function, rather than the new one. So I’m still looking for solutions.

I’ve spent some time thinking and I think I understand better what I’m looking for. What I’m really looking for is a way to specify a default implementation of a type entry in a module signature. Is this kind of thing possible? I’m suspecting that it isn’t, and that the expected solution is to simply duplicate code in all of the modules matching the signature?

Yeah, it seems very possible with include, e.g.

module type Joinable = {
  type elem
  type t<'a>

  let join: (t<elem>, t<elem>) => t<elem>
}

module Int = {
  type elem = int
}

module JoinableArrayInt = {
  include Int
  type t<'a> = array<'a>

  let join = Js.Array.concat
}

I don’t know about ReScript, but in OCaml and Standard ML, where “Module Functions” (Functors) originated, there is a style called “fully functorial style” where every module is the product of some functor, in some cases the unit functor, ie, module M = () => { ... }.

This style usually relies on efficient treatment in the compiler, i.e., the elimination of abstractions. This elimination is usually through expansion and renaming, which does lead to duplication. In practice however, it tends to be the case the duplication is somewhat bounded in nature. One advantage of duplication is that it can often trigger further optimization because types are known to be different.

That said, I don’t know enough about the ReScript compiler internals to know exactly what it does in this case.