Hi everyone ,
I recently started using ReasonML, and have been trying to make use of the first class modules pattern (described here) to make my ReasonML program testable. My program uses a module for Axios, which I purify first using a Functor. I would like my program to easily switch implementations for the Axios module for testing purposes. So far, this is what I came up with (I boiled it down to pseudo-code):
// Axios.re
module type S = {
type t; // type of the instance
let create: () => t
let get: (t, string) => Js.Promise.t(string)
}
module RealImpl: S = {
type t;
[@bs.module "axios"] external create: unit => t = "create";
let get: (t, string) => Js.Promise.resolve("A real value")
}
module FakeImpl: S = {
type t = unit;
let get: (t, string) => Js.Promise.resolve("A fake value")
}
// AxiosIO.re
// Purifies the API of Axios
module Make = (A: Axios.S) => {
let get = (url, axiosInstance: A.t): IO.t(string, Js.Promise.error) =>
IO.toIOLazy(() => axiosInstance->A.get(url))
}
// Main.re
let makeIOProgram(module A: Axios.S) => {
module AIO = AxiosIO.Make(A);
let axiosInstance = A.create()
axiosInstance |> AIO.get("foo") // Works!
}
But what if I want to do something in a function, using the AxiosIO interface and axiosInstance
? For example, to define a client. To achieve that, I first defined an interface:
// AxiosIO.re
module type S = {
type t
let get: (string, t) => IO.t(string, Js.Promise.error);
};
and let AxiosIO adhere to that interface:
// AxiosIO.re
module Make = (A: ADTooling.AxiosS): S => {
type t = A.t
...
}
Then, I would like to program against this interface
// Client.re
let make = (module A: AxiosIO.S, axiosInstance: A.t) => {
let getBusinessValue = axiosInstance |> A.get("business entity")
getBusinessValue
}
Note that I really want to pass the axiosInstance
to this make
function, as I want to setup logging etc. for it first. Unfortunately, this yields the error
This pattern matches values of type
A.t
but a pattern was expected which matches values of type
’a
The type constructor A.t would escape its scope
Using parametrized types:
// Client.re
let make = (module A: Interface, axiosInstance: 'a) => {
let getBusinessValue = axiosInstance |> A.get("business entity")
getBusinessValue
}
yields the same error:
This has type:
A.t =>
IO.t(string, Js.Promise.error)
But somewhere wanted:
'a => 'd
The type constructor A.t would escape its scope
From here I have no clue how to proceed, as my knowledge of ocaml is very limited . How would you approach this problem?