-
Notifications
You must be signed in to change notification settings - Fork 33
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
Having types as possible module arguments #40
base: master
Are you sure you want to change the base?
Conversation
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.
The RFC is lacking the part where you argue why this feature is desirable/useful. Do you have compelling examples? (I know from private communication that there are some examples coming from modular implicits, and I think that it would be interesting to explain them, but maybe there are also compelling examples without them today?)
One way to look for examples would be to look for uses of sig type t end
in OCaml codebases in the wild. Here is for example a Github search URL, I have not looked at the results much.
rfcs/type_arguments_for_modules.md
Outdated
This RFC proposes to extend a bit the module language by adding the possibility | ||
of giving types arguments to modules. | ||
|
||
The goal of this feature is to simplify replace cases where the user might want |
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.
typo: to simplify cases
rfcs/type_arguments_for_modules.md
Outdated
``` | ||
|
||
|
||
## Important restraints |
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.
restrictions
rfcs/type_arguments_for_modules.md
Outdated
Only type paths should be authorized when doing an application inside | ||
a type path. | ||
|
||
In other words, `M(type <m: int>).t` should be rejected. |
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 find the wording of this paragraph obscure. I'm not fully sure what you mean by "type path", and "inside a type path" is also not clear. I believe that you are talking about type-functor applications that show up inside type expressions: M(type foo).t
; but can you give more details on the restriction on foo
, for example by listing the allowed constructions?
(cc @yallop who may be interested in this feature) |
You can look at example also via this sherlocode search |
The counter example
is indeed a counter example if the compilation would not be compiled as a function.
|
I asume that the encoding is :
stand for
where |
This isn't clear to me. Have you considered the alternative of using a value restriction? In current OCaml, type arguments to functions, like this let f (type a) = ... don't have run-time representations, and type arguments to constructors, like this let f (T (type a) (x : a)) = ... don't have run-time representations, so it's a bit non-uniform for type arguments to modules to have run-time representations. Type arguments to functions don't delay evaluation let f (type a) = begin print_endline "ok" end (* prints "ok" *) so it's rather surprising if type arguments to modules do delay evaluation: module M (type a) = struct print_endline "ok" end (* prints nothing *) For type arguments to functions, the usual value restriction applies, so in the following, let f (type a) =
let r : a option ref = ref None in
... Could a similar approach work for type arguments to modules? Continuing the analogy, type arguments to functions don't need to be supplied explicitly (though perhaps it'd be useful to have the option to do so). Could a similar approach work for type arguments to modules? |
Side-note: I find it surprising that |
I was actually wondering the same as Jeremy. I understand that the proposal is just syntactic sugar for the encoding I proposed, hence the actual semantics. However, it might be more interesting to not make it syntactic sugar and restric the body to be a value form. If we chose this sematics and restriction, the actual behavior when the body is not a value form can be recovered by passing an extra empty structure |
In the case where we we want to keep the descripted semantic with explicit application by the user, we can remove the application if the functor is pure. Because all applications will give the same result thus it can be pre-computed. I think the proposal from @diremy of not handling this as syntactic sugar and restricting the body to be a value seems to be a good idea. The biggest argument being that we can still obtain the actual behavior by adding the argument that is currently added at runtime in the proposal.
I think we might have cases where being able to explicitly give the type argument is still beneficial. I agree that sometimes not writing that argument could increase concision (such as |
I don't think that handling this as an optional argument works well. Consider the case where you abstract over several type arguments in sequence, there is no way at application time to indicate which of the arguments you have in mind on explicit application. I'm of two minds regarding application expliciteness:
|
The propose syntax was to write |
I think that this is a question that is weighting on @diremy's mind. Note: Coq/Rocq allows the application syntax |
After some thinking I arrived at the conclusion that using value restriction was indeed the best direction to go. This could allow optimization with reduced module allocation and functor application. But also you could still have a computing semantic using a module instead of a type. However some question would need some decisions about what we want :
module M (type a) = struct let r = ref None end Should be rejected of accepted with signatrue module M2 (type a) = struct
let () = print_endline "Here"
module M3 (type a) = struct
...
print_endline "Here";;
...
end |
It is always easier to restrict things at first, and allow them later when a good use-case comes. I would therefore answer "reject" to both of your questions. |
I am not so convinced. The question is what specification do we give to the construct, and I see two options. OPTION 1We just say that type abstraction does not block the evaluation, then
should not be different from
which are currently both accepted. Then, why should we make an exception to the behavior and fail in the former and not in the later? OPTION 2Perhaps, we may add in the specification that type abstraction should only be used in front of pure modules and that it is the user's responsability to ensure that, although the typechecker will help and fail, when it can tell that |
The main problem is that if we have a non-blocking lambda, then the values As such when computing the type of module M(type a) = struct let r = ref None end We need to use weak value restriction to lower the scope of the unification variable module M2 = struct let r = ref None end However the difficulty with module M(type a) = struct
let r1 : a option ref = ref None
type t = A of a
let r2 : t option ref = ref None
end As such I propose to split the proposal in two step :
|
In this case (I missed these details in my previous comment) I agree that we should go for the two-step process, starting with the strict value restriction, and only see later if we need to relax it and use the weak value restriction. |
Nice, I can't wait to have this to experiment with float/rational/gmp as argument and see if I can have both a common definition for all functions and recover the performance of float. |
A proposed design for having types as module arguments.
Rendered version
In summary :