-
Notifications
You must be signed in to change notification settings - Fork 36
include functor
#43
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
base: master
Are you sure you want to change the base?
include functor
#43
Conversation
In a similar way could we also add a This might be useful in some other cases and does not seem much work in addition to this feature. The use case that I have in mind would be linked to modular implicits : module type Eq = sig
type t
val eq : t -> t -> bool
end
module type Ord = sig
type t
val cmp : t -> t -> int
module E : Eq with type t = t
end
module F (X : sig type t val ord : t -> t -> int) : Ord with type t = X.t = body
module OInt = struct
type t = int
let cmp = Int.compare
module functor E = F
end This allows for the |
Indeed. I agree this is a reasonable feature and not much more work. It has occasionally been requested by users of |
This is a pattern that is actually quite common in the flambda2 code base, in particular module T = struct
module M = struct
type t = ...
let compare = ...
end
include M
module Set = Set.Make(M)
end Which would allow to get rid of that spurious module T = struct
type t = ...
let compare = ...
module functor Set = Set.Make
end One such example in the upstream compiler code base: |
Have you considered the alternative of giving a name (say " module M = struct
type t = ...
[@@deriving compare, sexp]
include functor Comparable.Make
end you'd write module M = struct
type t = ...
[@@deriving compare, sexp]
include Comparable.Make(_)
end ? With that alternative design it'd be possible to refer to the module prefix in arbitrary module expressions rather than always passing it as the argument of a single-parameter functor, so you could also write things like: include F(_)(X) and module type of _ and open F(_) and module E = F(_) and include S with module type T = _ and perhaps even type t = F(_).t etc. |
Just to be sure : do the components used to "fill in" the parameter need to be defined from the current structure, or do they only need to be visible at this point (coming from a surrounding structure or from some |
A better argument against using underscore to talk about the beginning of the module is that current work on modular implicits. We are currently thinking of defining |
They need to be defined from the current structure. One could imagine doing either thing, but this has a nice clear rule, makes it less likely refactorings will cause errors due to what is in scope for include functor changing, and simplifies the implementation. |
For syntax, we could use use just plain old
Or we could be even bolder and use a symbol:
I think any syntax should not be available in paths. |
Personally, I dislike both the: module functor E = F form and mechanisms based on a name for the contents of the current module, and would prefer to push people towards sig
type t
module Set : Set.S with type elt = t
end and use that, rather than having each user choose the name for their set module.
module type MixS = (X : OrderedType) -> sig module Set : S with type elt = X.t end
module Mix : MixS and then you can write: module Foo : sig
type t
include functor Set.MixS
end = struct
type t = [...]
let compare = [...]
include functor Set.Mix
end |
If I understand correctly, this use of |
That is one way to look at it and it does look different from other uses of module types in that perspective. An alternative though is to consider |
If I understand correctly, module F = functor (Y:S) -> struct (* ... *) end
module Foo = struct
(* code *)
include functor F
end could be replaced by changing module F = functor (Y:S) -> struct include Y (* ... *) end
module Foo = F(struct
(* code *)
end) Overall, could the role of |
I think this is a reasonable idea, but doesn't quite offer the full convenience of module M = struct
module T = struct
type t = ...
[@@deriving compare, sexp]
end
include T
include Comparable.Make(T)
end I think your proposal saves the |
It can save struct
open (struct module X = M end)
include X
include F(X)
end Then the example of the RFC would become: module M = Comparable.Make [reexport] (struct
type t = ...
[@@ deriving compare, sexp]
end) I think it provides more or less the same functionality. An upside is that it does not depend on a specific position in the code like module M = struct
type t = ...
include functor F
let x = 42
include functor G
end |
Actually a key issue with the re-export pattern I was suggesting is that the functor can only re-export the field indicated in its parameter signature, which seems much more restricted than |
This has sat for a while and there is some unresolved debate about the best design. @Octachron, could I request that the language committee take this RFC up? Thanks! |
Having the committee relaunch the debate sounds sensible to me, I will keep you updated once we have a shepherd. |
The proposal suggests to allow Question: have you considered having |
Hello, Here is my two cents. Regarding the proposed mechanism, I believe that it is clearly useful. I have commonly felt the need to say "please apply the functor Regarding the concrete syntax, I rather dislike This suggests that perhaps the new feature that is really needed is not |
I find that @yallop's suggestion of naming the "current module so far" has merit, at least when equipped with the decent syntax @lpw25 if I understand correctly, your argument is that you prefer to extend modules by inclusion, rather than by naming new submodules, because this tends to encourage a coherent style where the same module names are reused consistently. So you like From a distance, I'm not convinced:
On the other hand, one could argue that the following ought to work, which has comparable expressivity to @yallop's proposal, and could be presented as easier to understand than a magical include functor (functor Self => F(Self)(X)) (This doesn't quite work today because there is no syntax nor bidirectional-propagation mechanism to have the signature of |
@fpottier: |
In fact, rather than a keyword for the name, we might allow naming the current module as we do for objects... |
What problem do you see with signatures? Can't we just use |
You got me. Indeed, in the presence of nested modules, there might conceivably be a need for multiple levels of It is a bit unsettling that |
This is an intriguing idea, but I don't think that it is acceptable... the name of the current module would look like a variable, as its name is chosen by the user, but it is not a variable in the usual sense, since its meaning changes every time a new definition is made in the current module. |
Sure the meaning changes over time, but this is exactly the same problem with |
I also thought of |
Do I understand correctly that your idea is for I think that's a reasonable idea. I tend to prefer the current form because I think it encourages people to give a real name to these functor types rather than relying on |
(Slightly cheeky suggestion.) If folks are worried that
Note that
|
The references to the In exchange for deviating from well-trod paths and introducing binding to the mix -- which is of course notoriously easy to understand -- we get a small improvement in expressivity. This just seems like a bad trade to me. |
Personally I am rather reassured by the |
I don't understand the point about binding. In what sense is |
|
That's the part I understand. But it seems to me that the unnamed argument of |
I agree with @lpw25 that a non-recursive shallow mixin construct is usefull and well-understood, and should not require a new form of binding. Instead of seeing module M = struct
(* decls1 *)
include functor F
(* decls2 *)
end would be sugar for : module M = struct
include (
struct
(* decls1 *)
end mixin F )
(* decls2 *)
end Where the structure delimiters Note on mixinThe language is already expressive enough for non-recursive mixins (aka sequential mixins), with the caveat of the introduction of fresh names when handling anonymous structures :
module M = M1 mixin M2 is sugar for module M = struct
open (struct module Temp1 = M1 module Temp2 = M2 end)
include Temp1
include Temp2
end
module M = M1 mixin F is sugar for module M = struct
open (struct module Temp1 = M1 module Temp2 = F end)
include Temp1
include Temp2(Temp1)
end However in practice, avoidance and loss of type sharing would probably make such patterns a pain. |
I am willing to be convinced by On the other hand, I am less convinced by the naturality of (* the parametrized signature of sets,
as a signature-returning functor *)
module SetS1 (X : Set.OrderedType) = struct
module type S = Set.S with type elt = X.t
end
(* the parametrized signature of sets
as a functor type *)
module type SetS2 = functor (X : Set.OrderedType) ->
Set.S with type elt = X.t
(* Building a functor type from a
signature-returning functor seems to work. *)
module type SetS2' = functor (X : Set.OrderedType) ->
SetS1(X).S
(* Building a signature-returning functor
from a functor type does not work,
at least the following fails. *)
module SetS1' (X : Set.OrderedType) = struct
module type S = module type of SetS2(X)
(* Error: the module type SetS2 is not a functor,
it cannot be applied. *)
end
(* I think we could do this, but we get
a wider signature that includes X *)
module SetS1' (X : Set.OrderedType) = struct
module type S = sig
include X
include functor SetS2
end
end
|
Thinking out loud: if we had a language construct that takes a functor type |
I suppose the practical difference is that |
The distinction I'm drawing is that an identifier can be used deeply within the term. It is this depth which brings in all the various issues around identifiers e.g. if you nest them how does shadowing work etc. |
I am somewhat sympathetic to this point: The idea isn't at all unusual when you consider how mixins and inheritance usually to work. You have some "mixin type" or "class type" that you use to both classify the things you can inherit and also to create signatures for modules/classes constructed using inheritance. For us, the types of our mixins are just functor types, and so the thing used to create signatures for modules constructed using mixins should also be functor types. |
I wonder if the RFC could be improved a little bit so as to be more explicit about the two proposed features, namely I think I have understood that Regarding |
A very reasonable request. I am a bit swamped today, but will flesh out the bit about signatures in the proposal later in the week. |
@fpottier I've now pushed a commit that says more about signatures - sorry this took me a week to get back to! The new text incorporates some ideas from @gasche and @lpw25 from the discussion above, which I found helpful in explaining this construct. |
This is a proposal for a new structure and signature item form,
include functor
.Rendered version
(Thanks to @OlivierNicole and @goldfirere for help preparing this RFC)