I am new to Ocaml and I am wondering whether there is a way to store a module in a reference?
Specifically, I would like to create a module using a functor at the beginning of my program then use it aftewards (or even in other modules). The behavior would depend on some parameter (a user entry for instance). Something like :
Module MyFunctor = Functor ….
myRef = ref MyFunctor(RightBehaviour) (*default*)
If user_input = ‘right’ then
myRef := MyFunctor(LeftBehaviour)
End;
!myRef.DoSomething
Although your code example doesn’t look like OCaml code at all. If you’re using an alternate syntax, it’s possible that first-class modules are not supported.
We can reference a module, but the module type can’t be inferred with ref. This means you have to declare a module type:
module type DOERS = sig
val doSomething : int -> unit
end
module A = struct
let doSomething = print_int
end
module MyFunctor (M : DOERS) =
struct
let doSomething v =
print_string "value=";
M.doSomething v;
print_string "\n"
end
let myRef = ref (module A : DOERS)
let () =
if true then
myRef := (module MyFunctor(A));
let module M = (val !myRef) in
M.doSomething 42
Note the (module …) and (val …) syntax which converts a module into a value which can be referenced, passed as an argument to a function, etc… and vice-versa.
Beware of the case (if not If…) and end closes a begin. (Not required with an if and a single statement).
Thank you for your answer.
Now however, I have another problem.
If I want doSomething to print integers and strings (depending on the parameter of the functor) the compiler seams to be confused on the type of the parameter of doSomething since it does know it before runtime.
Specifically if I change your code this way :
module type DOERS = sig
type t
val doSomething : t -> unit
end
module A = struct
type t=int
let doSomething = print_int
end
module B = struct
type t=string
let doSomething = print_endline
end
module MyFunctor (M : DOERS) =
struct
type t = M.t
let doSomething v =
print_string "value=";
M.doSomething v;
print_string "\n"
end
let myRef = ref (module A : DOERS)
let () =
if true then
myRef := (module MyFunctor(A));
let module M = (val !myRef) in
M.doSomething 42
I have an error :
31 | M.doSomething 42
^^
Error: This expression has type int but an expression was expected of type M.t
Of course it works if I do
let module Yy = MyFunctor(A) in
Yy.doSomething 42;
If you want your program to have a single reference (myRef) point to a module which deals with int or a string depending of a condition known at the compile time, you need a dynamic typing. But OCaml has a static typing. I guess you can’t do what you want directly.
You may use algebraic data type (type var = Int of int | String of string …) but since the type can’t be checked at the compile time, you have to use pattern matching and check the if you can deal with an int or a string….
let myRef = ref (module A : DOERS with type t = int)
is enough to solve your error.
But it will prevent you to affect to myRef a module where t is not int. Since I don’t know why you want the type t to be variable, I don’t know if it is an issue.
As I have said, OCaml has a static type system, then all types must be known at compile time. (And with the type erasure, the OCaml system prevents the definition of an introspection library or operator, well, Obj is an introspection library but very limited).
Let’s suppose you want to type:
if condition then
M.doSomething 42
else
M.doSomething "hello"
OCaml will infer for M.doSomething, two incompatible types. This can’t work whatever the M module definition.
module type DOERS = sig
val doSomething : unit -> unit
end
let refM =
let module A = struct
let v = 42
let doSomething () = print_int v
end in
ref (module A : DOERS)
let () = if true then
let module B = struct
let v = "hello"
let doSomething () = print_string v
end in
refM := (module B)
let () = let module M = (val !refM) in
M.doSomething ()
Here refM has simply the type DOERS… then you can affect any module (based on int, string, whatever). But you can only use fixed type functions (here doSomething () the internal integer is not available from refM)
The proposal:
module type DOERS = sig
type t
val from_int : int -> t
val doSomething : t -> unit
end
...
M.doSomething (M.from_int 42)
is a bit similar: you have to use fixed type fonction and the composition of doSomething and from_int has type int -> unit whatever the t type. With this version, you can access the intermediate value, but with an opaque type: you won’t be able to do much with it, and here a doSomethingWithInt : int -> () would be simpler.
But I find it hard to guess what you really want to do.
Ok. So if I get it right, unlike other dynamically typed languages (such as Java and Smalltalk), it is not possible to write a program that adapts their behavior at runtime. That is why, for instance, it is not possible to write a universal printer (a single function that can take a string or an integer as a parameter and print it)?
Types in OCaml gives a static/compile-time description of the behaviours of your program and functions. In other words, by definition types cannot change between different runtime execution. This also implies that runtime variables cannot peek at the compile-time description of their own behaviour to adjust their behaviour. This does not mean that a program cannot adapt its behaviour at runtime however.
Overall, your issue looks to me like an XY problem: putting modules into a reference is kind of doable but leans quite closely to the advanced edge of the language and is not idiomatic.
It would be probably simpler in the long run to restructure your code to avoid this pattern. For instance, you could make the generated module an explicit argument of functions and modules using it rather than using global state to pass it implicitly as an argument.
Having a general printer would be useful with variable which may contains different types depending of the execution… but this can’t happen with OCaml.
If you type let a = 42, you know a is an integer, then you are able to type afterward doSomethingWithInt a.
No problem.
And let a = if condition then 42 else "hello" will not compile.
But you can have genericity:
let doSomething a helper =
print_string "=";
helper a;
print_string "=\n";;
val doSomething : 'a -> ('a -> 'b) -> unit = <fun>
Here doSomething can work with any type of argument, but in order to use them efficiently, would need a compatible helper function. Functors can help making a module specialized for a type. Typically module IntSet = Set.Make(Int)… here the module Int provides the type and the helper function. But it will provide roughly the same end result: Set is generic, but creates IntSet which is specialized. You can’t have sets of anything.
A module which implement a type module signature can be seen as a set of virtual methods. But in the main program all types are known precisely.
I had proposed an example of a module which includes its value, then the doSomething can be call with a unit argument. This can achieve some polymorphic feature.
Maybe if I describe a bit what I am trying to do it will be clearer.
Actually, I would like to write a program that may invoque two different tools depending on the file type passed in parameter (.t1 → tool1, .t2->tool2).
The two tools have different data structures.
So, my idea was (more or less inspired by my previous experience with OO) create a module depending on the target tool, put it in a reference then use this reference to access the desired “behavior”. Something like :
let myRef = ref (module A : DOERS)
let () =
if (someConditionOfTheTypeOfDocument) then
myRef := (module MyFunctor(A));
let module M = (val !myRef) in
....
And later if a module needs a computation implemented in MyFunctor :
Then you have to abstract out of your two different tools a common uniformly typed module interface from which you can construct your first class modules, just as you would in designing the interface of your base class for your OO solution.
I suspect that a more natural approach would be to wrap the program logic in a functor which can then operate on a module passed as a parameter. Consider:
module Program (M: S) = struct
let () =
M.doSomething whateverStructureIReadFromExternalTool
end
let () =
if (someConditionOfTheTypeOfDocument) then
let module M = Program (A) in ()
else
let module M = Program (B) in ()
If whateverStryctureReadFromExternalTool may have different OCaml type (dependent of the document type), we would have to write.
module Program (M: S) = struct
let () =
let str = M.readFromExternal () in
M.doSomething str
end
let () =
if (someConditionOfTheTypeOfDocument) then
let module M = Program (A) in ()
else
let module M = Program (B) in ()
The type of str will typically be in the S signature and instantiated by A and B.