Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove/Rename handler terminology - refactor api #35

Merged
merged 18 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Changed

- Simplify the API and remove the term 'handler' from the project (breaking change) (#35, @mbarbin).
- Change the API to make the library Type-Safe (breaking change) (#34, @mbarbin, @v-gb).
- Register custom trait names instead of extensible variant names (#31, @mbarbin).

### Deprecated
Expand Down
6 changes: 3 additions & 3 deletions doc/docs/reference/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@

**Trait**: A module signature grouping some functionality.

**Implemenation**: A module that implements a Trait signature.
**Implementation**: A module that implements a Trait signature.

**Parametrized Library**: A library that requires the functionality contained in one or several Traits. It can compile without having access to actual Trait implementations.

**Binding**: The pair of a Trait and an actual implementation for it.

**Handler**: A list of bindings.
**Provider**: A list of bindings.

**Provider**: A handler coupled with its required internal state (an OCaml value that behaves like an object, but isn't one).
**Packed Provider**: A provider coupled with its required internal state (an OCaml value that behaves like an object, but isn't one).
21 changes: 9 additions & 12 deletions doc/docs/reference/hello_world.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,22 @@ type show = [ `Show ]

module Show : sig
val t : ('t, (module S with type t = 't), [> show ]) Provider.Trait.t
end = struct
type (_, _, _) Provider.Trait.t +=
| Show : ('t, (module S with type t = 't), [> show ]) Provider.Trait.t
end = Provider.Trait.Create (struct
type 'a module_type = (module S with type t = 'a)
end)

let t = Show
end

let print (Provider.T { t; handler }) =
let module M = (val Provider.Handler.lookup handler ~trait:Show.t) in
let print (Provider.T { t; provider }) =
let module M = (val Provider.lookup provider ~trait:Show.t) in
print_endline (M.show t)

let string_provider t =
let handler =
Provider.Handler.make
[ Provider.Trait.implement Show.t
let provider =
Provider.make
[ Provider.implement Show.t
~impl:(module struct type t = string let show = String.uppercase_ascii end)
]
in
Provider.T { t; handler }
Provider.T { t; provider }
```

```ocaml
Expand Down
35 changes: 16 additions & 19 deletions doc/docs/tutorials/getting-started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,34 +121,31 @@ If you are not using opam or dune, we'll assume you're an expert and know what t
To use Provider, first we have to create a new tag and a new type constructor that will be attached to our `READER` Trait. To do this, we:

- Create a tag type with a polymorphic variant that will be dedicated to our Trait.
- Add dynamically a new constructor to the `Provider.Trait.t` extensible variant. This uses an OCaml Language Extension named [Extensible variant types](https://ocaml.org/manual/5.2/extensiblevariants.html). This one has the particularity that it is also a [GADT](https://ocaml.org/manual/5.2/gadts.html#start-section)!
- Create a new trait with one of the `Provider.Trait.Create*` functors.

```ocaml
type reader = [ `Reader ]

module Reader : sig
val t : ('t, (module READER with type t = 't), [> reader ]) Provider.Trait.t
end = struct
type (_, _, _) Provider.Trait.t +=
Reader : ('t, (module READER with type t = 't), [> reader ]) Provider.Trait.t

let t = Reader
end
end = Provider.Trait.Create (struct
type 't module_type = (module READER with type t = 't)
end)
```

### Parametrized Library

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

```ocaml
module Show_files2 : sig

val print_files_with_ext : [> reader ] Provider.t -> path:string -> ext:string -> unit
val print_files_with_ext : [> reader ] Provider.packed -> path:string -> ext:string -> unit

end = struct

let print_files_with_ext (Provider.T { t = reader; handler }) ~path ~ext =
let module R = (val Provider.Handler.lookup handler ~trait:Reader.t) in
let print_files_with_ext (Provider.T { t = reader; provider }) ~path ~ext =
let module R = (val Provider.lookup provider ~trait:Reader.t) in
let entries = R.readdir reader ~path |> List.sort String.compare in
let files = List.filter (String.ends_with ~suffix:ext) entries in
files |> List.iter (fun file ->
Expand All @@ -162,19 +159,19 @@ end = struct
end
```

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

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

```ocaml
module Show_files3 : sig

val print_files_with_ext : [> reader ] Provider.t -> path:string -> ext:string -> unit
val print_files_with_ext : [> reader ] Provider.packed -> path:string -> ext:string -> unit

end = struct

let print_files_with_ext (Provider.T { t = reader; handler }) ~path ~ext =
let module R = (val Provider.Handler.lookup handler ~trait:Reader.t) in
let print_files_with_ext (Provider.T { t = reader; provider }) ~path ~ext =
let module R = (val Provider.lookup provider ~trait:Reader.t) in
let module M = Show_files (R) in
M.print_files_with_ext reader ~path ~ext

Expand All @@ -188,12 +185,12 @@ This is a sort of hybrid of the two versions! In a real-world scenario, you woul
In this section, we are showing what implementing a Trait looks like. This part is simplified, given that we already have implemented a version of our `Reader` Trait when we wrote `Sys_reader`. We're going to be able to re-use it here, and we are showing below really only the provider-specific bits:

```ocaml
let sys_reader () : [ `Reader ] Provider.t =
let sys_reader () : [ `Reader ] Provider.packed =
Provider.T
{ t = ()
; handler =
Provider.Handler.make
[ Provider.Trait.implement Reader.t ~impl:(module Sys_reader) ]
; provider =
Provider.make
[ Provider.implement Reader.t ~impl:(module Sys_reader) ]
}
```

Expand Down
Loading