Create a functor from a module containing public data and keep that data public

Hello
I am trying to write a functor that can generate an X_Flags module from another
X_FlagBits module containing public constants (constrained by an abstract type).
Is this even possible?

module type A_FLAG_BITS = sig
  type b = private int
  val bit1 : b
  val bit2 : b
  val bit3 : b
end
module A_FlagBits : A_FLAG_BITS = struct
  type b = int
  let bit1 = 0x00000001
  let bit2 = 0x00000002
  let bit2 = 0x00000004
end
module A_Flags : sig
  include A_FLAG_BITS
  type t = private int
  val set : b list -> t
  val isSet : t -> b list -> bool
end = struct
  include A_FlagBits
  type t = int
  let set (l:b list) = List.fold_left (fun (acc:t) (bit:b) -> acc lor (bit :> int)) 0 l
  let isSet (flags:t) (bits:b list) =
    let flagbits = set bits in
    flags land flagbits = flagbits
end

In the example above, the functor would generate the A_Flags module from the A_FlagBits module.

Yes, it is possible. At least from your sample code I don’t see anything that would prevent turning A_Flags into a functor. Did you hit issues when you tried ? If so, maybe we could help you if you show us what you tried and what problems you encountered.

Here’s what I did:

module type FLAG_BITS = sig
  type b = private int
end

module Flags (Bits:FLAG_BITS) = struct
  include Bits
  type t = int
  let set (l:b list) = List.fold_left (fun (acc:t) (bit:b) -> acc lor (bit :> int)) 0 l
  let isSet (flags:t) (bits:b list) : bool =
    let flagbits = set bits in
    flags land flagbits = flagbits
end
module A_Flags = Flags(A_FlagBits)

So far, so good, but when I want to do

A_Flags.(isSet myflags [bit1])

I get the following error message: Unbound value bit1.

Indeed, since the input variable of the functor Flags was typed with FLAG_BITS, all constants of A_FlagBits were masked.

Ok, so the issue is that you went from this:

module type A_FLAG_BITS = sig
  type b = private int
  val bit1 : b
  val bit2 : b
  val bit3 : b
end

to this:

module type A_FLAG_BITS = sig
  type b = private int
end

My guess is that you want to be able to generate flag modules from multiple lists of bits, so you cannot put the individual bits in the signature of the functor. This gets us into interesting (i.e. complicated) concepts.

The first thing to note is that functors are not macros: they have to be statically typed independently of their arguments, so they cannot return a structure where fields may or may not exist depending on the argument.
We can try to cheat around this using a feature called module aliases: you can express that a module expression (including a submodule in the result of a functor) is the same as another module. Ideally we would like to write something like that:

module type FLAG_BITS = sig
  type b = private int
end

module Flags (Bits:FLAG_BITS) = struct
  module B = Bits
  include Bits
  type t = int
  let set (l:b list) = List.fold_left (fun (acc:t) (bit:b) -> acc lor (bit :> int)) 0 l
  let isSet (flags:t) (bits:b list) : bool =
    let flagbits = set bits in
    flags land flagbits = flagbits
end
module A_Flags = Flags(A_FlagBits)

This seems to work (e.g. the compiler allows it), but if you look at the inferred type for the result you will see that it reads like this:

module Flags :
  functor (Bits : FLAG_BITS) ->
    sig
      module B : sig type b = Bits.b end
      type b = Bits.b
      type t = int
      val set : Bits.b list -> t
      val isSet : t -> Bits.b list -> bool
    end

The crucial point is that instead of module B = Bits (which is something that is allowed in a signature if functors are not involved), you see module B : sig type t = Bits.b end, which is almost equivalent but useless for our case.

This is a limitation of our current module system (if I remember correctly, @blement has ideas on how to list this restriction).

To unblock your problem, here is a solution that is slightly less satisfying but should work:

module A_Flags = struct
  include A_FlagBits
  include Flags(A_FlagBits)
end

@vlaviron That is indeed a use case for transparent ascription, which would allow to re-export an alias to a functor argument. The same code would get the better signature:

module Flags :
  functor (Bits : FLAG_BITS) ->
    sig
      module B : ( = Bits :> FLAG_BITS)
      type b = Bits.b
      type t = int
      val set : Bits.b list -> t
      val isSet : t -> Bits.b list -> bool
    end

where (= Bits :> FLAG_BITS) indicates that B is an alias of Bits seen through the signature FLAG_BITS. In other words, B was obtained by restricting Bits to the signature FLAG_BITS, which might have remove/reordered fields, but the ones still there are the same.

However in this particular case, I don’t get why extending FLAG_BITS to also contain some value fields bit1, bit2, etc. wouldn’t work, but I’m also unsure about the overall setup.

1 Like