• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mbarbin / provider / 208

27 Mar 2026 08:03AM UTC coverage: 99.118% (-0.9%) from 100.0%
208

Pull #51

github

web-flow
Merge a740e4e58 into 781fe9b82
Pull Request #51: Use mdexp

164 of 170 new or added lines in 8 files covered. (96.47%)

674 of 680 relevant lines covered (99.12%)

7.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.89
/doc/docs/tutorials/getting-started/getting_started.ml
1
(*********************************************************************************)
2
(*  provider - Dynamic Dispatch with Traits                                      *)
3
(*  SPDX-FileCopyrightText: 2024-2025 Mathieu Barbin <mathieu.barbin@gmail.com>  *)
4
(*  SPDX-License-Identifier: ISC                                                 *)
5
(*********************************************************************************)
6

7
(* @mdexp
8

9
   # Getting Started
10

11
   In this tutorial, we'll create a small module that can find all files of a
12
   given extension in a directory and show the number of lines for each file. This
13
   module will be implemented in OCaml and will demonstrate the use of the Provider
14
   library.
15

16
   We'll create a functionality equivalent to the following bash script: *)
17

18
let run_sh cmd =
19
  Printf.printf "```sh\n$ %s\n" cmd;
1✔
20
  flush stdout;
1✔
21
  let exit_code = Sys.command (Printf.sprintf "bash -c %s" (Filename.quote cmd)) in
1✔
NEW
22
  if exit_code <> 0 then Printf.printf "[%d]\n" exit_code;
×
23
  print_string "```"
1✔
24
;;
25

26
let%expect_test "bash example" =
27
  run_sh "for file in $(ls -1 *.txt | sort) ; do wc -l $file; done";
1✔
28
  (* @mdexp.snapshot *)
29
  [%expect
1✔
30
    {|
31
    ```sh
32
    $ for file in $(ls -1 *.txt | sort) ; do wc -l $file; done
33
    5 hello.txt
34
    ```
35
    |}]
1✔
36
;;
37

38
(* @mdexp
39

40
   ## Library Requirements
41

42
   The library will be parametrized by the ability to:
43
   - List the entries from a directory.
44
   - Load the contents of a file from disk.
45

46
   We'll instantiate this library with an implementation based on OCaml Stdlib.
47

48
   ## Using a Functor
49

50
   We'll start in familiar territory by making a first attempt using a functor. A
51
   functor in OCaml is a module that is parametrized by another module. This allows
52
   us to create flexible and reusable code.
53

54
   ### Trait
55

56
   We'll use the term "Trait" to refer to the functionality we depend on in the
57
   parametrization. This is essentially a module signature that operates on a given
58
   type. The terminology is inspired by Rust. Here is our `READER` Trait:
59

60
   @mdexp.code *)
61

62
module type READER = sig
63
  (** A type to hold some environment, could be [unit] if you are
64
      using [Unix], [Eio.Stdenv.t], etc. *)
65
  type t
66

67
  (** List the entries present in the directory at a given path. *)
68
  val readdir : t -> path:string -> string list
69

70
  (** Returns the contents of a file at a given path. *)
71
  val load_file : t -> path:string -> string
72
end
73

74
(* @mdexp
75

76
   ### Parametrized Library
77

78
   With the `READER` Trait defined, we can now implement `Show_files`. Since we are
79
   defining `Show_files` as a functor, we can write logic that depends on the
80
   abilities provided by the `READER` Trait, even though we do not yet have access
81
   to an actual implementation for that Trait.
82

83
   @mdexp.code *)
84

85
module Show_files (Reader : READER) : sig
86
  val print_files_with_ext : Reader.t -> path:string -> ext:string -> unit
87
end = struct
88
  let print_files_with_ext reader ~path ~ext =
89
    let entries = Reader.readdir reader ~path |> List.sort String.compare in
2✔
90
    let files = List.filter (String.ends_with ~suffix:ext) entries in
2✔
91
    files
2✔
92
    |> List.iter (fun file ->
93
      let contents = Reader.load_file reader ~path:(Filename.concat path file) in
2✔
94
      let line_count =
2✔
95
        List.length (String.split_on_char '\n' contents)
2✔
NEW
96
        - if String.ends_with ~suffix:"\n" contents then 1 else 0
×
97
      in
98
      Printf.printf "%d %s\n" line_count file)
99
  ;;
100
end
101

102
(* @mdexp
103

104
   ### Provider
105

106
   A provider supplies implementations for a set of Traits. Let's create an
107
   implementation for the `READER` Trait based on OCaml Stdlib.
108

109
   @mdexp.code *)
110

111
module Sys_reader : READER with type t = unit = struct
112
  (* Sys doesn't need any internal environment. *)
113
  type t = unit
114

115
  let readdir () ~path = Sys.readdir path |> Array.to_list
3✔
116
  let load_file () ~path = In_channel.with_open_bin path In_channel.input_all
3✔
117
end
118

119
(* @mdexp
120

121
   ### Runtime Instantiation
122

123
   Now it is time to instantiate our library, assuming we are in some client code
124
   that will decide on which provider to supply to our parametrized library:
125

126
   @mdexp.code *)
127

128
module My_show_files = Show_files (Sys_reader)
129

130
(* @mdexp
131

132
   And then use it:
133

134
   @mdexp.code *)
135

136
let%expect_test "functor instantiation" =
137
  My_show_files.print_files_with_ext () ~path:"." ~ext:".txt";
1✔
138
  [%expect
1✔
139
    {|
140
    5 hello.txt
141
  |}];
1✔
142
  ()
143
;;
144

145
(* @mdexp
146

147
   So far, we've done nothing with the Provider library. Please hang on, that's
148
   what the next section is about!
149

150
   ## Using Provider
151

152
   ### Installation
153

154
   Provider is available through opam:
155

156
   ```sh
157
   $ opam install provider
158
   ```
159

160
   Then, make sure to add `provider` (the name of the library) to your dune file
161
   (and deps in `dune-project`).
162

163
   If you are not using opam or dune, we'll assume you're an expert and know what
164
   to do!
165

166
   ### Trait
167

168
   To use Provider, first we have to create a new tag and a new type constructor
169
   that will be attached to our `READER` Trait. To do this, we:
170

171
   - Create a tag type with a polymorphic variant that will be dedicated to our
172
   Trait.
173
   - Create a new trait with one of the `Provider.Trait.Create*` functors.
174

175
   @mdexp.code *)
176

177
type reader = [ `Reader ]
178

179
module Reader : sig
180
  val t : ('t, (module READER with type t = 't), [> reader ]) Provider.Trait.t
181
end = Provider.Trait.Create (struct
182
    type 't module_type = (module READER with type t = 't)
183
  end)
184

185
(* @mdexp
186

187
   ### Parametrized Library
188

189
   Now that we're switching to using Provider, our module is no longer a functor.
190
   Rather, each of the functions that need provider functionality will take it as an
191
   extra parameter. The type `[> reader ] Provider.packed` indicates that the
192
   provider required needs to implement *at least* the `reader` Trait, but it is
193
   allowed to implement other Traits too (the other bindings will be ignored).
194

195
   @mdexp.code *)
196

197
module Show_files2 : sig
198
  val print_files_with_ext
199
    :  [> reader ] Provider.packed
200
    -> path:string
201
    -> ext:string
202
    -> unit
203
end = struct
204
  let print_files_with_ext (Provider.T { t = reader; provider }) ~path ~ext =
205
    let module R = (val Provider.lookup provider ~trait:Reader.t) in
1✔
206
    let entries = R.readdir reader ~path |> List.sort String.compare in
1✔
207
    let files = List.filter (String.ends_with ~suffix:ext) entries in
1✔
208
    files
1✔
209
    |> List.iter (fun file ->
210
      let contents = R.load_file reader ~path:(Filename.concat path file) in
1✔
211
      let line_count =
1✔
212
        List.length (String.split_on_char '\n' contents)
1✔
NEW
213
        - if String.ends_with ~suffix:"\n" contents then 1 else 0
×
214
      in
215
      Printf.printf "%d %s\n" line_count file)
216
  ;;
217
end
218

219
(* @mdexp
220

221
   Notice how we've slightly changed the beginning of the implementation of
222
   `print_files_with_ext`. This time around, we are finding the module `Reader` by
223
   doing a provider lookup, based on the Trait we are interested in.
224

225
   The rest of the implementation hasn't actually changed one bit compared to our
226
   first functor example. You can get further convinced by this last sentence,
227
   considering the following tweak:
228

229
   @mdexp.code *)
230

231
module Show_files3 : sig
232
  val print_files_with_ext
233
    :  [> reader ] Provider.packed
234
    -> path:string
235
    -> ext:string
236
    -> unit
237
end = struct
238
  let print_files_with_ext (Provider.T { t = reader; provider }) ~path ~ext =
239
    let module R = (val Provider.lookup provider ~trait:Reader.t) in
1✔
240
    let module M = Show_files (R) in
241
    M.print_files_with_ext reader ~path ~ext
242
  ;;
243
end
244

245
(* @mdexp
246

247
   This is a sort of hybrid of the two versions! In a real-world scenario, you
248
   would probably not carry both versions around, so this is just for the sake of
249
   the example (although, perhaps in certain cases, it can make sense to have both
250
   styles around. You'll decide on a case-by-case basis).
251

252
   ### Provider
253

254
   In this section, we are showing what implementing a Trait looks like. This part
255
   is simplified, given that we already have implemented a version of our `Reader`
256
   Trait when we wrote `Sys_reader`. We're going to be able to re-use it here, and
257
   we are showing below really only the provider-specific bits:
258

259
   @mdexp.code *)
260

261
let sys_reader () : [ `Reader ] Provider.packed =
262
  Provider.T
2✔
263
    { t = ()
264
    ; provider = Provider.make [ Provider.implement Reader.t ~impl:(module Sys_reader) ]
2✔
265
    }
266
;;
267

268
(* @mdexp
269

270
   ### Runtime Instantiation
271

272
   Same as earlier, assuming we're now in client code, it is time to commit to a
273
   runtime implementation and instantiate a provider!
274

275
   @mdexp.code *)
276

277
let my_sys_reader = sys_reader ()
2✔
278

279
(* @mdexp
280

281
   We can then move on to enjoying the functionality offered by the parametrized
282
   library.
283

284
   @mdexp.code *)
285

286
let%expect_test "provider instantiation" =
287
  Show_files2.print_files_with_ext my_sys_reader ~path:"." ~ext:".txt";
1✔
288
  [%expect
1✔
289
    {|
290
    5 hello.txt
291
  |}];
1✔
292
  Show_files3.print_files_with_ext my_sys_reader ~path:"." ~ext:".txt";
293
  [%expect
1✔
294
    {|
295
    5 hello.txt
296
  |}];
1✔
297
  ()
298
;;
299

300
(* @mdexp
301

302
   ## Conclusion
303

304
   In this tutorial, we've created a Trait, a library parametrized by it, a
305
   provider implementing that Trait, and finally some user code invoking the library
306
   with this provider, providing a complete tour of the functionality offered by the
307
   library.
308

309
   More complex cases would involve providers implementing multiple Traits,
310
   parametrized libraries with functions expecting multiple Traits as well (with
311
   some arbitrary overlap). You'll also have the ability to conditionally depend on
312
   the availability of certain Traits implementation at runtime.
313

314
   This granularity allows different providers to select which Traits to implement.
315
   They can even choose to cover only part of the functionality required by a
316
   parametrized library, leaving some functions aside. This provides a level of
317
   flexibility that is not achievable with a monolithic functor. *)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc