Impredicative
Impredicative

Reputation: 5069

Extend module with type alias

I'm trying to work with the ocaml-inotify package. The relevant parts for this question can be defined as follows

module Inotify : sig
  type wd
  val int_of_wd : wd -> int
end = struct
  type wd = int
  let int_of_wd wd = wd
end

This work is taking place in a setuid script, and I want the inotify parts to be handled unprivileged, so I'm forking and then setuid-ing down to an unprivileged user in the child. However, this means that I need to pass the wd entities back to the parent through a pipe, and so need to serialise and deserialise them, requiring a int_to_wd function.

I've tried extending the module as follows:

module Rich_Inotify : sig 
  include module type of Inotify with type wd := int
  val wd_of_int : int -> wd
end = struct
  include Inotify
  let wd_of_int (wd:int) : wd = wd 
end
module Inotify = Rich_Inotify

However, the compiler complains that wd is an int and not a wd. How do I persuade it that these types are the same?

Upvotes: 2

Views: 878

Answers (1)

gasche
gasche

Reputation: 31459

If the Inotify module is really defined with this signature, that is sealed with an abstract type, there is no way you will be able to break its abstraction to re-define it as an int. I mean that your signature (module type of Foo with wd := int) is clever and describes the interface you want, but the implementation Foo does not satisfy it: if the type-checker allowed that, there would be no type abstraction at all.

You should request the ocaml-inotify maintainer to add marshalling primitives (and possibly fork it locally to do that if you need). Instead of exposing the fact that wd = int, he or she could either publish conversion functions to and from int (so that future implementation changes could still implement these functions), or directly to and from string if you're only interested in marshalling and want to expose less internal details.

There is a solution that exposes more internal details that back-and-forth conversions to an abstract type, which is to use private type abbreviations:

sig
  type t = private int
  val mk : int -> t
end

This signature exposes that the internal representation is int, and allows to cast from t to int explicitly with (foo :> int) or (foo : t :> int) (you know that this is a no-op at runtime), but does not allow the inverse cast, so you are forced to use the mk function which, I assume, will do some kind of range checking to make sure that this is a valid descriptor.

(Some people will probably suggest that, as a workaround, you break the type safety of the language by using an unsafe cast that should not be named. Do not; this is bad advice.)

Upvotes: 4

Related Questions