Search code examples
typesmodulesmlfunctor

How to re-export datatype from functor argument in Standard ML


Is it possible in Standard ML to re-export the constructors of a datatype that's part of a structure received as a functor argument. Some code will probably make this easier to understand:

signature FLAG =
  sig
    type t
  end

signature MEMBER =
  sig
    structure Flag : FLAG
  end

functor Member(F : FLAG) : MEMBER =
  struct
    structure Flag = F
  end

structure M =
  Member(struct
    datatype t =
      FLAG_1
    | FLAG_2
  end)

val flag1 = M.Flag.FLAG_1;
(* Error: unbound variable or constructor: FLAG_1 in path M.Flag.FLAG_1 *)

The above example may not make any sort of practical sense, but it's just a watered down version of an issue that I encountered in one of my projects.


Solution

  • If I understand the situation correctly, the unelaborated type specification in the FLAG signature entails that t is left opaque, and thus inaccessible for anything outside of the structures that implement FLAG.

    Generally speaking, in SML, if a signature specifies an interface for a module, then the only parts of that module that can be accessed from without are those which are explicitly described in the signature. As you likely know, if you specify an interface for some module, then only those functions and values you explicitly declare in the signature are provided for use; all those which are omitted become sealed inside the module. The same principle is at work here with the unexplained specification of type t: since the signature does not give any account of how this type is constituted, no information about it is available.

    So you can easily re-export the value constructors from a module given as a parameter to a functor, provided you have included those constructors in the specifications of that module's interface. E.g.,

    signature FLAG =
    sig
        datatype t = FLAG_1 | FLAG_2
    end
    
    signature MEMBER =
    sig
        structure Flag : FLAG
    end
    
    functor Member(F : FLAG) : MEMBER =
    struct
        structure Flag = F
    end
    
    structure M =
    Member(struct
            datatype t =
                     FLAG_1
                   | FLAG_2
            end)
    

    And then

    - val a = M.Flag.FLAG_1;
    val a = FLAG_1 : ?.t
    

    The most important point to take note of here is probably this: the inaccessibility of the value constructors in the module implementing FLAG have everything to do with the way the interface is specified and nothing to do with the fact that it appears here as a parameter to functor Member. We get the same behavior you observed when you were using a functor with the following program:

    signature FLAG =
    sig
        type t
    end
    
    structure F : FLAG =
    struct
        datatype t =
                 FLAG_1
               | FLAG_2
    end
    

    And then

    [opening ~/Programming/sml/scratch/scratch.sml]
    signature FLAG = sig type t end
    structure F : FLAG
    val it = () : unit
    - F.FLAG_1;
    stdIn:63.1-63.9 Error: unbound variable or constructor: FLAG_1 in path F.FLAG_1