Non-abstract types redundancy in Signature/Functor pattern - module

With the Signature/Functor pattern, I refer to the style of Map.S / Map.Make in the OCaml standard library. This pattern is highly successful when you want to parameterize a large piece of code over some type without making it fully polymorphic. Basically, you introduce a parameterized module by providing a signature (usually called S) and a constructor (Make).
However, when you take a closer look, there is a lot of redundancy in the declaration:
First, both the signature and the functor have to be announced in the .mli file
Second, the signature has to be repeated completely in the .ml file (is there actually any legal way to differ from the .mli file here?)
Finally, the functor itself has to repeat all definitions again to actually implement the module type
Summa summarum, I get 3 definition sites for non-abstract types (e.g. when I want to allow pattern matching). This is completely ridiculous, and thus I assume there is some way around. So my question is two-fold:
Is there a way to repeat a module type from an .mli file in an .ml file, without having to write it manually? E.g. something like ppx_import for module signatures?
Is there a way to include a module type in a module inside an .ml file? E.g. when the module type has only one abstract type definition, define that type and just copy the non-abstract ones?

You can already use ppx_import for module signatures. You can even use it in a .ml to query the corresponding .mli.
If a module is composed only of module signatures, you can define the .mli alone, without any .ml. This way you can define a module, let's say Foo_sigs, containing the signature and use it everywhere else.

Repeating type and module type definitions can be avoided to move them to external .ml file. Let's see the following example:
module M : sig
(* m.mli *)
module type S = sig
type t
val x : t
end
module type Result = sig
type t
val xs : t list
end
module Make(A : S) : Result with type t = A.t
end = struct
(* m.ml *)
module type S = sig
type t
val x : t
end
module type Result = sig
type t
val xs : t list
end
module Make(A : S) = struct
type t = A.t
let xs = [A.x;A.x]
end
end
Instead of writing two files m.mli and m.ml, I used a module M with an explicit signature: this is equivalent to have the two files and you can try it on OCaml toplevel by copy-and-paste.
In M, things are duped in sig .. end and struct .. end. This is cumbersome if module types become bigger.
You can share these dupes by moving them to another .ml file. For example, like the following n_intf.ml:
module N_intf = struct
(* n_intf.ml *)
module type S = sig
type t
val x : t
end
module type Result = sig
type t
val xs : t list
end
end
module N : sig
(* n.mli *)
open N_intf
module Make(A : S) : Result with type t = A.t
end = struct
(* n.ml *)
open N_intf
module Make(A : S) = struct
type t = A.t
let xs = [A.x;A.x]
end
end
You can also use *_intf.mli instead of *_intf.ml, but I recommend using *_intf.ml, since:
Module packing does not take mli only modules into account therefore you have to copy *_intf.cmi at installation.
Code generation from type definitions such as ppx_deriving needs things defined in .ml. In this example, it is no the case since there is no type definition.

In that specific case, you can just skip the .mli part:
Your abstraction is specified by the .ml
Reading it makes it quite clear (as people know the pattern from the stdlib)
Everything that you'd put in the .mli is already in the .ml
If you work in a group that's requiring you to actually give a mli, just generate it automatically by using the ocamlc -i trick.
ocamlc -i m.ml >m.mli # automatically generate mli from ml
I know it doesn't exactly answer your question, but hey, it solves your problem.
I know that always putting a mli is considered to be best practice, but it's not mandatory, and that may be for some very good reasons.
As for your second question, I'm not sure I understood it well but I think this answers it:
module type ToCopy = sig type t val f : t -> unit end
module type Copy1 = sig include ToCopy with type t = int end
module type Copy2 = ToCopy with type t = int;;

Adding to camlspoter's answer and since the question mentions pattern matching, maybe you want to "re-export" the signatures and types with constructors declared in N_intf so they are accessible through N instead. In that case, you can replace the open's with include and module type of, i.e.:
module N_intf = struct
type t = One | Two
(* n_intf.ml *)
module type S = sig
type t
val x : t
end
module type Result = sig
type t
val xs : t list
end
end
module N : sig
(* n.mli *)
include module type of N_intf
module Make(A : S) : Result with type t = A.t
end = struct
(* n.ml *)
include N_intf
module Make(A : S) = struct
type t = A.t
let xs = [A.x;A.x]
end
end
Then you'll get the following signatures:
module N_intf :
sig
type t = One | Two
module type S = sig type t val x : t end
module type Result = sig type t val xs : t list end
end
module N :
sig
type t = One | Two
module type S = sig type t val x : t end
module type Result = sig type t val xs : t list end
module Make : functor (A : S) -> sig type t = A.t val xs : t list end
end
Now the constructors One and Two can be qualified by N instead of N_intf, so you can ignore N_intf in the rest of the program.

Related

How can I access image constructors for a pattern-matching in Vg, Ocaml?

I'm trying to re-write the equal function from the Vg.I module for Ocaml but when I try to do a pattern-matching with an image (of type t) I got an error.
Here is my code :
open Vg;;
open Gg;;
let rec decompose i = match i with
| I.Primitive x -> Printf.printf "primitive\n\n"
| I.Cut(a,p,i) -> Printf.printf "cut\n\n"; decompose i
| I.Cut_glyphs(a,r,i) -> Printf.printf "cut_glyphs\n\n"; decompose i
| I.Blend(b,a,i1,i2) ->
Printf.printf "blend: t1\n\n"; decompose i1;
Printf.printf "blend: t2\n\n"; decompose i2
| I.Tr(tr,i) -> Printf.printf "tr\n\n"; decompose i
| _ -> failwith "some error";;
and here is the error
| I.Primitive x -> Printf.printf "primitive\n\n"
^^^^^^^^^^^
Error: Unbound constructor I.Primitive
I've also tried 'Vg.Primitive' and just 'Primitive' (even if it didn't make a lot of sense '^^) but I've got the same error every time.
If anyone knows how to properly use these constructors in a pattern-matching it would really help
Thanks in advance
The type image of the Vg library is abstract.
This means that the Vg library considers that the explicit definition of
this type is an implementation detail that no external users should rely upon.
In particular, different variants of the library may use different implementation for this type.
Consequently, you should not pattern match on values of this type, and OCaml module system enforces that you cannot do so.
To complement octachron's excellent answer, consider a simple contrived example. A module A which contains a type t. Internally this type has a constructor A_ that takes an int. But the module's signature does not expose this type constructor. We only know the type exists, but nothing else about it. Values of that type, therefore can only be manipulated via the functions the module exposes.
Within the module A, that type can be pattern-matched as normal.
module A : sig
type t
val make : int -> t
val get_val : t -> int
end = struct
type t = A_ of int
let make x = A_ x
let get_val (A_ x) = x
end
utop # A.A_ 42;;
Error: Unbound constructor A.A_
utop # A.make 42;;
- : A.t = <abstr>
utop # A.(make 42 |> get_val);;
- : int = 42

External and internal interfaces & information hiding in OCaml

When creating a library from multiple modules, I cannot find a nice way to do proper information hiding to the user of the library (external interface) while being able to access everything I need on the internal interface.
To be more specific, I have two modules (Files a.ml[i] and b.ml[i]). In A, I define some type t, thats internals I don't want to hide from the user (external interface).
module A : sig
type t
end
module A = struct
type t = float
end
In module B, I then want to use the secret type of A.t.
module B : sig
create_a : float -> A.t
end
module B = struct
create_a x = x
end
This of course does not compile, because the compilation unit of B does not know the type of A.t.
Solutions I know, but don't like:
Move the function create_a to module A
Copy the definition of A.t to B and cheat the type checker with some external cheat : `a -> `b = "%identity"
Is there some other way to know the type of A.t in B without leaking this information to the library's interface?
As always an extra layer of indirection can solve this problem. Define a module Lib that will specify an external interface, e.g.,
module Lib : sig
module A : sig
type t
(* public interface *)
end
module B : sig
type t
(* public interface *)
end = struct
module A = A
module B = B
end
If you don't want to repeat yourself and write module signatures twice, then you can define them once in a module sigs.ml:
module Sigs = struct
module type A = sig
type t
(* public interface *)
end
(* alternatively, you can move it into sigs_priv.ml *)
module type A_private = sig
include A
val create_a : float -> t
end
...
end
Finally, make sure that you're not installing interfaces (the .cmi files),
during your installation step, so that users can't bypass your abstraction. If your're using oasis, then it is simple: just make all your modules internal, except the module Lib, i.e., specify them with InternalModules field.

Multiple arguments in functor, OCaml

I have the following (fairly abstract) piece of OCaml code, in which the last line gives an error "Syntax error: ')' expected" which is extremely vague for me
module type AT =
sig
type t
end;;
module type BT =
sig
type t
type a
end;;
module A : AT =
struct
type t = int
end;;
module B : BT =
struct
type a
type t = a list
end;;
module type ABT =
sig
type t
module InsideA : AT
module InsideB : BT
end;;
module ABT_Functor (AArg:AT) (BArg:BT with type a = AArg.t) : ABT =
struct
module InsideA = AArg
module InsideB = BArg
type t = Sth of InsideA.t * InsideB.t
end;;
module ABTA = ABT_Functor (A);;
module ABTAB = ABT_Functor (A) (B:BT with type a = A.t);;
However, when I change the last line to
module ABTAB = ABT_Functor (A) (B);;
I get a signature mismatch error, saying
Modules do not match:
sig type t = B.t type a = B.a end
is not included in
sig type t type a = A.t end
Type declarations do not match:
type a = B.a
is not included in
type a = A.t
But I don't really understand that error.
So, I hope it's quite clear what I want to achieve - I'd like to provide structures A and B to ABT_Functor functor, to obtain a structure ABTAB. How should I do that?
The general issue is that there are no constraints between module types AT and BT from the start, but you explicitely require one for your functor, so you need to provide one explicitely when using it if you force the module arguments to their minimal signatures.
In your precise case, the module A and B have been coerced respectively to AT and BT, without any additional information, thus making their inner types abstract. When passing them to the functor, even with additional type constraints, you cannot recover the implicitly existing relationship between A and B as it has been erased.
module A: AT with type t = int = struct type t = int end
module B: BT with type a = int = struct type a = int type t = int list end
(* with these definitions, you may use A and B without further ado as arguments to your functor *)
module ABTAB = ABT_Functor(A)(B) (* it works *)
Note that if you had not constrained A and B to begin with, it would have worked straight away.
module A = struct type t = int end
module B = struct type a = int type t = int list end
(* no constraints above *)
module ABTAB = ABT_Functor(A)(B)
Hm, your original version does not type-check either (the last line does not even parse), and for the same reason.
The reason is simple: in your definition of B you are implementing a as follows:
type a
Such a definition produces a distinct abstract type, i.e. a new type that is different from any other type. As a definition (not to be confused with a specification of a type in a signature), such a type is useless for pretty much anything but phantom types, because you cannot actually create any values of this type.
Nor can you change the definition of the type after the fact. This:
module Bint : sig type a = int end = B
is already ill-typed. Type B.a was defined as different from int. It only equals itself.
The problem you see with your functor application follows from there.

Difference between module <name> = struct .. end and module type <name> = struct.. end?

module <name> =
struct
..
end;;
module type <name> =
struct (* should have been sig *)
..
end;;
The first declares a module and the second declares a module type (aka a signature). A module type contains type and val declarations, whereas a module can contain definitions (e.g., let bindings). You can use a signature to restrict the type of a module, much as you might for a function. For example,
module type T = sig
val f : int -> int
end
module M : T = struct
let f x = x + 1
let g x = 2 * x
end
Now, we have
# M.f 0 ;;
- : int = 1
# M.g 0 ;;
Error: Unbound value M.g
M.g is unbound because it's hidden by the signature T.
Another common way to use module types is as arguments to and return values of functors. For example, the Map.Make functor in the standard library takes a module with signature Map.OrderedType and creates a module with signature Map.S
P.S. Note that there's a mistake in the question. A module type is declared using
module type <name> = sig
...
end
A structure (written struct … end) is a bunch of definitions. Any object in the language can be defined in a module: core values (let x = 2 + 2), types (type t = int), modules (module Empty = struct end), signatures (module type EMPTY = sig end), etc. Modules are a generalization of structures: a structure is a module, and so is a functor (think of it as a function that takes a module as argument and returns a new module). Modules are like core values, but live one level above: a module can contain anything, whereas a core value can only contain other core values¹.
A signature (written sig … end) is a bunch of specifications (some languages use the term declaration). Any object in the language can be specified in a module: core values (val x : int), types (type t = int), modules (module Empty : sig end), signatures (module type EMPTY = sig end), etc. Module types generalize signatures: a module type specifies a module, and a module type that happens to specify a structure is called a signature. Module types are to modules what ordinary types are to core values.
Compilation units (.ml files) are structures. Interfaces (.mli files) are signatures.
So module Foo = struct … end defines a module called Foo, which happens to be a structure. This is analogous to let foo = (1, "a") which defines a value called foo which happens to be a pair. And module type FOO = sig … end (note: sig, not struct) defines a module type called FOO, which happens to be a signature. This is analogous to type foo = int * string which defines a type called foo which happens to be a product type.
¹
This is in fact no longer true since OCaml 3.12 introduced first-class modules, but it's close enough for an introductory presentation.
module type describe a module. It is the same as the difference between .ml and .mli

Understanding functors in OCaml

I'm quite stuck with the following functor problem in OCaml. I paste some of the code just to let you understand. Basically
I defined these two modules in pctl.ml:
module type ProbPA = sig
include Hashtbl.HashedType
val next: t -> (t * float) list
val print: t -> float -> unit
end
module type M = sig
type s
val set_error: float -> unit
val check: s -> formula -> bool
val check_path: s -> path_formula -> float
val check_suite: s -> suite -> unit
end
and the following functor:
module Make(P: ProbPA): (M with type s = P.t) = struct
type s = P.t
(* implementation *)
end
Then to actually use these modules I defined a new module directly in a file called prism.ml:
type state = value array
type t = state
type value =
| VBOOL of bool
| VINT of int
| VFLOAT of float
| VUNSET
(* all the functions required *)
From a third source (formulas.ml) I used the functor with Prism module:
module PrismPctl = Pctl.Make(Prism)
open PrismPctl
And finally from main.ml
open Formulas.PrismPctl
(* code to prepare the object *)
PrismPctl.check_suite s.sys_state suite (* error here *)
and compiles gives the following error
Error: This expression has type Prism.state = Prism.value array
but an expression was expected of type Formulas.PrismPctl.s
From what I can understand there a sort of bad aliasing of the names, they are the same (since value array is the type defined as t and it's used M with type s = P.t in the functor) but the type checker doesn't consider them the same.
I really don't understand where is the problem, can anyone help me?
Thanks in advance
(You post non-compilable code. That's a bad idea because it may make it harder for people to help you, and because reducing your problem down to a simple example is sometimes enough to solve it. But I think I see your difficulty anyway.)
Inside formulas.ml, Ocaml can see that PrismPctl.s = Pctl.Make(Prism).t = Prism.t; the first equality is from the definition of PrismPctl, and the second equality is from the signature of Pctl.Make (specifically the with type s = P.t bit).
If you don't write an mli file for Formulas, your code should compile. So the problem must be that the .mli file you wrote doesn't mention the right equality. You don't show your .mli files (you should, they're part of the problem), but presumably you wrote
module PrismPctl : Pctl.M
That's not enough: when the compiler compiles main.ml, it won't know anything about PrismPctl that's not specified in formulas.mli. You need to specify either
module PrismPctl : Pctl.M with type s = Prism.t
or, assuming you included with type s = P.t in the signature of Make in pctl.mli
module PrismPctl : Pctl.M with type s = Pctl.Make(Prism).s
This is a problem I ran into as well when learning more about these. When you create the functor you expose the signature of the functor, in this case M. It contains an abstract type s, parameterized by the functor, and anything more specific is not exposed to the outside. Thus, accessing any record element of s (as in sys_state) will result in a type error, as you've encountered.
The rest looks alright. It is definitely hard to get into using functors properly, but remember that you can only manipulate instances of the type parameterized by the functor through the interface/signature being exposed by the functor.