Correct use of modules and types

I’m trying to understand why the following code doesn’t work when you : ID is removed from module Username: ID = IDString module Hostname: ID = IDString.


module Time = Core.Time_float

module type ID = sig
  type t
  val to_string : string-> t
  val from_string : t -> string
  val (=) : t->t->bool
end
module IDString = struct
  type t = string
  let to_string x: t = x
  let from_string x: string = x
  let (=) = String.(=)
end

module Username: ID = IDString
module Hostname: ID = IDString

type session_info = 
{
  user : Username.t;
  host : Hostname.t;
  when_started: Time.t
}

let sessions_have_same_user s1 s2 = Username.(=) s1.user s2.host

If you run this, everything works as expected. (There is supposed to be a bug because we are comparing user to hosts in the last line.)

However, if I drop the “: ID” in Username and Hostname to module Username = IDString and module Hostname = IDString, respectively, this particular error goes away (the one about comparing hosts and users from the last line mentioned above).

Why?

Edit: One more question: what is the type of session_info.user? Is it string? Username.string? Username.t?

Edit 2: What is the difference between a struct and a record in OCaml?

Just a note that it is helpful if you point to where you got the example. I’ll do it now: Files, Modules, and Programs - Real World OCaml

The reason why the : ID annotation forces the two types to be different, is because it makes the types abstract:

module type ID = sig
  type t
  ...

In other words, the module type annotation hides the implementation details of the type, and exposes only an abstract view of it. So the compiler is not able to prove that Username.t and Hostname.t are the same type. If the module type annotation is removed, the underlying implementation type t = string is exposed, and the compiler knows they are the same.

The type of session_info.user is as defined:

type session_info =
  { user : Username.t
  ...

I.e. Username.t. What is Username.t? If you follow the book’s code sample, it is an abstract type. Its implementation is string, but this is not exposed to the outside world.

In OCaml struct is the syntax for defining a nested module. And a record is a type which contains one or more fields.

2 Likes

In the signature ID the type t is abstract (ie you can’t use the definition of t outside the module implementation). When saying that module Username has signature ID, its type t (which is an alias for string) is hidden from outside Username. As a consequence, there is no way to know that Username.t and Hostname.t are both equal to string. They’re both abstract and nothing can be assumed about them. If you remove the : ID in the definition of both modules, their contents is apparent to the subsequent code.

For more information you can have a look at this part of the manual, or this QA, or course chapter.

Other questions:

  • you can’t really write session_info.user because in your code session_info is a type, and you can only use the dot notation with values (that are records).
  • let’s say you have a value x of type session_info then x.user is of type Username.t which is abstract (again, nothing can be said about it) but from your code we know its concrete representation is string (although you can’t use that fact in your code)
  • a structure is an implementation of a module, while a record is a (kind of) type. You can check the manual here and there for more info.
1 Like