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

Allow type expressions to 'locally open' modules #9484

Closed
austindd opened this issue Apr 21, 2020 · 13 comments
Closed

Allow type expressions to 'locally open' modules #9484

austindd opened this issue Apr 21, 2020 · 13 comments

Comments

@austindd
Copy link

austindd commented Apr 21, 2020

Currently, OCaml allows value expressions to "locally open" modules, but it would be helpful to extend this feature for use in type expressions.

For example, take the following code:

module Abc = struct
    type a
    type b
    type c
end
type abc_group = (Abc.a * Abc.b * Abc.c)

I would like to be able to define abc_group with syntax similar to this:

type abc_group = let open Abc in (a * b * c)

This would drastically reduce the verbosity of complex type signatures, especially those involving nested modules. Used in a reasonable way, this could lead to more readable code.

As for the specific syntax, I lack the experience to know what makes the most sense, so I leave that discussion to those who know more about OCaml syntax.

Edit:
@aantron proposed the following syntax:

type abc_group = Abc.(a * b * c)

This syntax is less verbose, and probably less confusing to most people.

@aantron
Copy link
Contributor

aantron commented Apr 22, 2020

I think the syntax should be Abc.(a * b * c), as let is from the expression language, while Abc.a is in both the type and expression languages.

@austindd
Copy link
Author

austindd commented Apr 22, 2020

@aantron That makes a lot of sense to me. Your suggestion is less verbose, and also probably less confusing.

@austindd austindd changed the title Allow type expressions to locally open modules Allow type expressions to 'locally open' modules Apr 22, 2020
@austindd austindd reopened this Apr 22, 2020
@lthls
Copy link
Contributor

lthls commented Apr 22, 2020

If you want to do this with a released version of OCaml, here is a trick that works:

include struct
  open Abc
  type abc_group = (a * b * c)
end

This will define the type abc_group without opening Abc in the outside context.

@aantron
Copy link
Contributor

aantron commented Apr 22, 2020

@lthls That works for the specific example above, but of course not in general, since it is at the module item level, while Abc.(te) is at the type expression level. open Abc will shadow identifiers in the entire following type declaration (in fact, all of them, if there are multiple type declarations), while Abc.(te) shadows identifiers only in te. This difference is probably important in light of the pervasive use of t as a type name in OCaml.

@austindd
Copy link
Author

Indeed, the idea of locally opening modules in type expressions is useful for precisely the same reasons that we find it useful in ordinary value expressions.

@lthls, I use that trick myself sometimes, but I often run into the shadowing issue that @aantron pointed out. We might want to write a single type expression in which multiple unrelated modules are locally opened in different parts of the expression. If we use the trick you mentioned instead, we would open each of those modules in that context, which would likely lead to type-shadowing.

@austindd
Copy link
Author

Does anyone know how difficult this would be to implement?

@garrigue
Copy link
Contributor

I didn't think about potential parsing conflict, but otherwise there is no difficulty in implementing that.
However, one concern may be that it makes harder to understand type definitions (you have to keep in mind what is in the module you are opening).
I was also concerned about printing, but it should be fine: this is just a way to factories writing paths.

@austindd
Copy link
Author

austindd commented May 13, 2020

@garrigue Interesting point about potential parsing conflicts, but otherwise I think the usefulness of this feature is self-evident. Keeping track of types inside a locally opened module could be tricky, but is fundamentally no different than keeping track of values, other than the widespread use of type t in many modules.

One use case I had in mind is a function that looks like this:

type ('a, 'b, 'c, 'r_type, 'w_type) pipe =
  ('a, 'b, [> Stream.Readable.kind] as 'r_ype) Stream.Readable.subtype ->
  (('b, 'c, [> Stream.Writable.kind] as 'w_type) Stream.Writable.subtype as 'w_stream) ->
  'w_stream

This is a real-world type signature for the FFI binding to Stream.Readable.prototype.pipe function in the Node.js API (I am one of the authors for this library). If we could locally open modules in type expressions, we could have this:

type ('a, 'b, 'c, 'r_type, 'w_type) pipe = Stream.(
  Readable.(('a, 'b, [> kind] as 'r_type) subtype) ->
  Writable.(('b, 'c, [> kind] as 'w_type) subtype) as 'w_stream ->
  'w_stream
)

Sure, it's a very complicated type signature, so it will always feel verbose no matter what we do, but I don't think we should settle for highly redundant code. The first example is arguably more explicit, but the second example has a better signal-to-noise ratio.

All of this is subjective, so I will happily concede any criticisms.

P.S.

I do not mean to direct this argument toward you personally, @garrigue. I just want to help clarify how this feature can add value to OCaml code, for anyone reading this thread. Additionally, I would be happy to invest some of my own time & effort into implementing this feature.

@github-actions
Copy link

This issue has been open one year with no activity. Consequently, it is being marked with the "stale" label. What this means is that the issue will be automatically closed in 30 days unless more comments are added or the "stale" label is removed. Comments that provide new information on the issue are especially welcome: is it still reproducible? did it appear in other contexts? how critical is it? etc.

@github-actions github-actions bot added the Stale label May 17, 2021
@austindd
Copy link
Author

Bumping to keep this issue alive. I really think this could lead to a better programming experience for a lot of people.

@gasche gasche removed the Stale label May 18, 2021
@github-actions
Copy link

This issue has been open one year with no activity. Consequently, it is being marked with the "stale" label. What this means is that the issue will be automatically closed in 30 days unless more comments are added or the "stale" label is removed. Comments that provide new information on the issue are especially welcome: is it still reproducible? did it appear in other contexts? how critical is it? etc.

@github-actions
Copy link

github-actions bot commented Jul 3, 2023

This issue has been open one year with no activity. Consequently, it is being marked with the "stale" label. What this means is that the issue will be automatically closed in 30 days unless more comments are added or the "stale" label is removed. Comments that provide new information on the issue are especially welcome: is it still reproducible? did it appear in other contexts? how critical is it? etc.

@github-actions github-actions bot added the Stale label Jul 3, 2023
@gasche
Copy link
Member

gasche commented Jul 3, 2023

This has been implemented by @johnyob in #12044, and should be part of OCaml 5.2.

@gasche gasche closed this as completed Jul 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants