-
Notifications
You must be signed in to change notification settings - Fork 368
Improve some wording in the functors tutorial #2023
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
Changes from 9 commits
0058608
b9c95a5
e3ee4cd
9a8498a
2a3ea11
03758f0
722bd1b
57f5191
f558f95
cd87f3e
b443443
7438292
c6eeb26
5401b2d
b0e29ab
22f000d
1deafff
4023527
6afcdcd
98a2383
ac98365
fb1188d
2fcf239
3a8394e
999bb22
c1a6c07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -63,7 +63,7 @@ Here is how this reads (starting from the bottom, then going up): | |||||
|
|
||||||
| **Note**: Most set operations need to compare elements to check if they are the same. To allow using a user-defined comparison algorithm, the `Set.Make` functor takes a module the specifies both the element type `t` and the `compare` function. Passing the comparison function as a higher-order parameter, as done in `Array.sort`, for example, would add a lot of boilerplate code. Providing set operations as a functor allows specifying the comparison function only once. | ||||||
|
|
||||||
| Here is an example how to use `Set.Make`: | ||||||
| Here is an example of how to use `Set.Make`: | ||||||
|
|
||||||
| **`funkt.ml`** | ||||||
|
|
||||||
|
|
@@ -80,10 +80,6 @@ This defines a module `Funkt.StringSet`. What `Set.Make` needs are: | |||||
| - Type `t`, here `string` | ||||||
| - Function allowing to compare two values of type `t`, here `String.compare` | ||||||
|
|
||||||
| However, since the module `String` defines | ||||||
| - Type name `t`, which is an alias for `string` | ||||||
| - Function `compare` of type `t -> t -> bool` compares two strings | ||||||
|
|
||||||
| This can be simplified using an _anonymous module_ expression: | ||||||
| ```ocaml | ||||||
| module StringSet = Set.Make(struct | ||||||
|
|
@@ -94,6 +90,10 @@ end) | |||||
|
|
||||||
| The module expression `struct ... end` is inlined in the `Set.Make` call. | ||||||
|
|
||||||
| However, since the module `String` already defines | ||||||
| - Type name `t`, which is an alias for `string` | ||||||
| - Function `compare` of type `t -> t -> bool` compares two strings | ||||||
|
|
||||||
| This can be simplified even further into this: | ||||||
| ```ocaml | ||||||
| module StringSet = Set.Make(String) | ||||||
|
|
@@ -157,14 +157,14 @@ let _ = | |||||
|
|
||||||
| This allows the user to seemingly extend the module `String` with a submodule `Set`. Check the behaviour using `opam exec -- dune exec funkt < dune`. | ||||||
|
|
||||||
| ## Functors Allows Parametrising Modules | ||||||
| ## Functors Facilitate Parametrising Modules | ||||||
neuroevolutus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ### Functors From the Standard Library | ||||||
|
|
||||||
| A functor is almost a module, except it needs to be applied to a module. This turns it into a module. In that sense, a functor allows module parametrisation. | ||||||
|
|
||||||
| That's the case for the sets, maps, and hash tables provided by the standard library. It works like a contract between the functor and the developer. | ||||||
| * If you provide a module that implements what is expected, as described the parameter interface | ||||||
| * If you provide a module that implements what is expected, as described by the parameter interface | ||||||
| * The functor returns a module that implements what is promised, as described by the result interface | ||||||
|
|
||||||
| Here is the module's signature that the functors `Set.Make` and `Map.Make` expect: | ||||||
|
|
@@ -245,6 +245,7 @@ module Binary(Elt: OrderedType) : S = struct | |||||
| type elt = | (* Replace by your own *) | ||||||
| type t = | (* Replace by your own *) | ||||||
| (* Add private functions here *) | ||||||
| let empty = failwith "Not yet implemented" | ||||||
| let is_empty h = failwith "Not yet implemented" | ||||||
| let insert h e = failwith "Not yet implemented" | ||||||
| let merge h1 h2 = failwith "Not yet implemented" | ||||||
|
|
@@ -371,7 +372,71 @@ let _ = | |||||
|
|
||||||
| Check the program's behaviour using `opam exec -- dune exec funkt < dune`. | ||||||
|
|
||||||
| **Note**: The functor `IterPrint.Make` returns a module that exposes the type from the injected dependency (here first `List.t` then `Array.t`). That's why a `with type` constraint is needed. When parametrising other something not exposed by the module (and _implementation detail_), the `with type` constraint is not needed. | ||||||
| **Note**: Modules received and returned by `IterPrint.Make` both have a type `t`. The `with type ... :=` constraint is needed to make the two `t` identical. This allows functions from the injected dependency and result module to use the same type. When the parameter's contained type is not exposed by the result module (i.e. when it is an _implementation detail_), the `with type` constraint is unnecessary. | ||||||
|
||||||
| **Note**: Modules received and returned by `IterPrint.Make` both have a type `t`. The `with type ... :=` constraint is needed to make the two `t` identical. This allows functions from the injected dependency and result module to use the same type. When the parameter's contained type is not exposed by the result module (i.e. when it is an _implementation detail_), the `with type` constraint is unnecessary. | |
| **Note**: Modules received and returned by `IterPrint.Make` both have a type `t`. The `with type ... :=` constraint is needed to make the two `t` modules identical. This allows functions from the injected dependency and result module to use the same type. When the parameter's contained type is not exposed by the result module (i.e., when it is an _implementation detail_), the `with type` constraint is unnecessary. |
It reads funny to me to have a plural (two) with just t -- is it accurate to have t modules instead? ...or t types?
Regardless, there needs to be a comma after i.e.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching that. I made sure to add the comma in that part.
With regards to the t, I think it may be fine to keep it as is since the t refers to two types rather than modules per se. What are your thoughts, @cuihtlauac?
neuroevolutus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
cuihtlauac marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
neuroevolutus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
neuroevolutus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
neuroevolutus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
cuihtlauac marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
cuihtlauac marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
neuroevolutus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your point. I must admit I had never fallen into that pitfall and wasn't aware of it.
What do you think about turning the narrative into something like:
tfromwith typetakes over localtwhich only has a local scope?
Again, the idea is to make things more direct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree; that sounds good. It sounds clearer to me as a reader.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used "takes precedence over" in the rewording, but let me know if you feel it's clearer with just saying "takes over."
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the intended use case for empty types (see https://ocaml.org/manual/emptyvariants.html for an explanation of why they were introduced). An abstract type works better as a placeholder for the use case you are describing. I would thus avoid mentioning empty variant types before the GADT introduction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. That would make sense. I'll revert the corresponding commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've edited the
Binaryfunctor example to makeeltandtabstract types.Thanks for the link to the manual! I'll check that out.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To give a better example of what I meant by "abstract type are better placeholder", if you write
the function
fis total: the only list of empty values is the empty list. But this is property is only true for empty types. In other words, by using empty types as placeholders one might accidentally capture properties that are only true for empty types.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I think I better understand now. As an aside, are there any examples in the wild where using empty types and refutation cases for them come in handy when it comes to GADTs? I think I'm still fuzzy on the motivation for empty types.