Skip to content

Conversation

@neuroevolutus
Copy link
Contributor

This PR adds some minor grammatical and wording changes to the functors tutorial that will hopefully make things clearer for new users of OCaml.

In particular, I made some changes to the aside about the with type constraint that may need particular review to ensure I did not state something that is semantically incorrect.

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**: The functor `IterPrint.Make` exposes the type from the injected dependency (here first `List.t` then `Array.t`) as the type `t` required of the result module. That's why a `with type` constraint is needed. When customising a type that's not exposed by the result module (i.e. an _implementation detail_), the `with type` constraint is not needed. As a further consequence of this, we could even define a local type `t` within the `struct` block for use within the implementation, and such a type `t` would not shadow the type `t` of the result module.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @neuroevolutus, that note needs to be clarified. Your text is better than is initial one, do you think this improves on it:

Suggested change
**Note**: The functor `IterPrint.Make` exposes the type from the injected dependency (here first `List.t` then `Array.t`) as the type `t` required of the result module. That's why a `with type` constraint is needed. When customising a type that's not exposed by the result module (i.e. an _implementation detail_), the `with type` constraint is not needed. As a further consequence of this, we could even define a local type `t` within the `struct` block for use within the implementation, and such a type `t` would not shadow the type `t` of the result module.
**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.

Regarding your last sentence:

As a further consequence, we could even define a local type t within the struct block for use within the implementation, and such a type t would not shadow the type t of the result module.

This is a bit hard to grasp because it requires 1/ figuring out what the code would look like if modified with a local t and 2/ figuring out the shadowing concern.

However, I believe you have a valid point. This is a matter that needs to be addressed. Could we address it somewhere else in the document or do we need an additional example? We may even need a complete section to address naming and shadowing concerns in relationship with functor definitions.

What is our opinion on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for the rewording. I think it's a lot more succinct and even clearer now. I made sure to commit your suggestion.

This is a bit hard to grasp because it requires 1/ figuring out what the code would look like if modified with a local t and 2/ figuring out the shadowing concern.

I agree; that is a good point 😅. I think adding a new section with a concrete example to address these concerns would be really nice. I'll work on adding this section and make a new commit to get your feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a small aside, I added a small section on empty variants to the data types tutorial to give context for the use of a singular | as a placeholder type definition.

@neuroevolutus neuroevolutus changed the title Improve some wording in the functors tutorial Improve some wording for the functors tutorial Feb 6, 2024
Copy link
Collaborator

@cuihtlauac cuihtlauac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @neuroevolutus, this is great! I wonder if the difference between = and := type constraint is needed. But overall, you are absolutely right, this matter is missing in the current version.

end
```

In the example above, the local type `t` does not shadow the type `t` exposed by the `with type` constraint and can be used for the implementation of the functor. However, it is generally better to avoid availing of this behaviour since it may make your code more difficult to understand.
Copy link
Collaborator

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:

t from with type takes over local t which only has a local scope?

Again, the idea is to make things more direct.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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."

Check the program's behaviour using `opam exec -- dune exec funkt < dune`.

**Note**: The functor `IterPrint.Make` exposes the type from the injected dependency (here first `List.t` then `Array.t`) as the type `t` required of the result module. That's why a `with type` constraint is needed. When customising a type that's not exposed by the result module (i.e. an _implementation detail_), the `with type` constraint is not needed. As a further consequence of this, we could even define a local type `t` within the `struct` block for use within the implementation, and such a type `t` would not shadow the type `t` of the result module.
**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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
**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.

Copy link
Contributor Author

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
Copy link
Contributor Author

I wonder if the difference between = and := type constraint is needed. But overall, you are absolutely right, this matter is missing in the current version.

Actually, this is something I was struggling with a bit. I realised when playing around with the code that with type ... = ... parses just fine but creates a compilation error. I read the section on modules in the OCaml manual, but it seemed to describe the with type ... = ... constraint and not the with type ... := ... one. Is there some intuition I'm missing on the difference between = and :=?

```

Such types are not ordinarily useful in OCaml programs (as they do not have any constructible values), but they can be useful as temporary placeholders when defining types in a [functor](/docs/functors#writing-your-own-functors).

Copy link
Member

@Octachron Octachron Feb 6, 2024

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've edited the Binary functor example to make elt and t abstract types.

Thanks for the link to the manual! I'll check that out.

Copy link
Member

@Octachron Octachron Feb 6, 2024

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

type empty = |
let f ([]: empty list) = ()

the function f is 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.

Copy link
Contributor Author

@neuroevolutus neuroevolutus Feb 6, 2024

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.

@neuroevolutus neuroevolutus changed the title Improve some wording for the functors tutorial Improve some wording in the functors tutorial Feb 6, 2024
@cuihtlauac
Copy link
Collaborator

I wonder if the difference between = and := type constraint is needed. But overall, you are absolutely right, this matter is missing in the current version.

Actually, this is something I was struggling with a bit. I realised when playing around with the code that with type ... = ... parses just fine but creates a compilation error. I read the section on modules in the OCaml manual, but it seemed to describe the with type ... = ... constraint and not the with type ... := ... one. Is there some intuition I'm missing on the difference between = and :=?

I believe we can move forward with this PR without exposing the difference between the
= and := constraints (see section 10.4 of the manual). We can add more material on that later

Copy link
Collaborator

@cuihtlauac cuihtlauac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Thnaks @neuroevolutus

@cuihtlauac cuihtlauac merged commit ffe2358 into ocaml:main Feb 12, 2024
@neuroevolutus
Copy link
Contributor Author

This looks good to me. Thnaks @neuroevolutus

Thank you so much for all the feedback! I'm so happy to have had the chance to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants