Passing Configuration Envirnoment in OCaml

Viewed 58

I'm trying to understand and/or find examples of how robust OCaml applications deal with a configuration environment. I'm coming from a world of Haskell, where I would use the Reader monad to solve this problem. In particular, I want to define a top-level configuration which I may pass throughout my application, but only some of the functions in my application will need to use the configuration.

To motivate this, consider a simple executable which queries a database. I would want to define a top-level main file which might do things like setup a connection pool, or create a logger which would be shared throughout the application.

I'm assuming OCaml has some pattern for dealing with this, but I cannot find any great examples. I'd prefer not define my own Reader monad and functorize the entire application if I don't have to.

That being said, this is also ugly

main.mli

module Config : sig
    type t = {
        fooConfig : Foo.Config.t;
        dbClientConfig : DB_client.Config.t;
    }
    val make : Foo.Config.t -> DB_client.Config.t -> t
end

val main : Config.t -> unit
foo.mli

module Config : sig
    type t = {
        dbConfig : DB_client.Config.t;
    }
end

(** fooFunc will need to pass the dbConfig, but it does not actually explicitly need anything in the config **)
val fooFunc : Config.t -> string -> unit 
db_client.mli

module Config : sig
    type t = {
        connPool : SomeConnPool
    }
end

(** Finally, I need to use the config to grab a connection out of the pool **)
val writeToDb : Config.t -> string -> (string, string) Lwt.result

I don't want to make the explicit arguments of function "higher in the execution stack" depend explicitly on a configuration they will never use.

Is there a nice functional pattern to deal with this nested dependency coupling? I'd appreciate any code examples that someone can point towards so I can study a better approach.

0 Answers
Related