While we do not have "unlet", we do have the ability to restrict a module definition by subtyping it to a stricter signature, which achieves the inverse effect: module V2_add = (V2 : sig val ( + ) : V2.t -> V2.t -> V2.t end) V2.(ox + oy) -- shadow ox, oy? V2_add.(ox + oy) -- only + We probably don't use the restricted signatures more often because it's quite verbose: we have to repeat the types of each binding, and we can't use the syntax (M : ...) everywhere. Those are syntax errors for example: open (V2 : ...) -- but include (V2 : ...) works! (V2 : ...).( ox + oy ) So we have to go through a named module, and it gets horribly verbose. Maybe we can steal the Haskell solution here, where the tendency is to list every imported binding: import V2 (ox, oy, ...) -- note the lack of types This is (sometimes) nicer for a reader, since the origin of a name is directly visible, and it's explicit that we only use a subset of each opened module -- so that you know which definitions of V2 you have to check. Now, let's imagine that we had such a light syntax, maybe: (M with val ox, oy, (+), ...) -- for the vague resemblance with existing syntax (M for ox, oy, ...) -- looks lighter, but the keyword is unfortunate then we could write this explicit form, which should not deserve any warning: let ox = (V2 for dot, ( * )).((dot v ox) * ox) in (V2 for ( * ), ( + )).(3 * ox + oy) let open (V2 for dot, ( + ), ( * )) in let ox = (dot v ox) * ox in 3 * ox + oy Sure, it's a lot more explicit than the other alternatives proposed in this thread, but it might also be beneficial outside of the M.( ... ) sugar situation. Random clarifications: - (M for x, ...) would be a shortcut for (M : sig val x : (module type of M).x ... end), which isn't even valid. - We would probably want (M for type t) too, but constructors and record fields are problematic, since we have to import them all (?). - (M for x as y) could be a nice extension for renaming and not shadow x. - Bonus: open (M for x) can correctly report M.x as missing if it has been renamed. Also, while I would appreciate "unlet x in", I don't think that we want to be negatively informative when it comes to opening a module: (M without x) isn't failproof regarding what remains; and the proposed annotation [@shadow x] looks good enough for the purpose of scripting the warning heuristic. On Wed, Aug 19, 2015 at 5:55 PM, Simon Cruanes wrote: > This whole thread makes me wonder whether local opens are worth it. I > don't like global open (at all), and shadowing is harmful even in smaller > scopes. Local open seems to be used for DSL that have a lot of infix > operators (maths, etc.) as demonstrated by the proposal of new warnings and > syntax about shadowing of infix operators. > > If modules have short names (Z, Q from Zarith come to mind, but > module-aliasing your favorite monad to "M" would do too), would M.+ be a > reasonable infix operator? I would be ready to have slightly more verbose > calls to M.>>= if it removes ambiguity and potential shadowing bugs. Of > course I don't know if this is compatible with the current syntax. > > > Le 19 août 2015 00:26:00 UTC+02:00, Anthony Tavener < > anthony.tavener@gmail.com> a écrit : >> >> (TL;DR: I commonly want to specify paths -uniquely- for identifiers in >> local >> scopes (function, module) which have no means to do this.) >> >> As I run into this yet again, moments ago... >> >> I do often want the ability to be clear that something is not to be >> shadowed >> by the opened scope... to specify it's from the local (usually function) >> scope. >> Part of the reason is for code clarity, but also to safeguard against >> possible >> later changes in the *opened* module (introducing the same identifier). >> >> let init flags = >> M.(init (flag1 + flag2 + flags)) (* flags is intended to be 'local', >> but it could be shadowed by a value in M *) >> >> Where M provides 'init', 'flag1', 'flag2', and '+', but 'flags' is in the >> local (function) context. When I do this I try to think of a way to make >> it >> self evident that 'flags' is not from M, but there is no way. Aside from >> bringing it outside the local-open, but then it's more difficult to build >> an expression. >> >> Vimscript might be one of the worst languages to use as a reference, but >> this >> issue does bring to mind the scope prefixes... >> >> let init flags = >> M.(init (flag1 + flag2 + l:flags)) (* illustrative -- not a proposal! >> *) >> >> I sometimes consider using naming conventions, but I don't want to >> explicitly >> make function arguments something like l_flags, l_point, etc. That would >> be a >> horrible widespread style, and doesn't work nicely with named arguments. >> Plus, changing names to do this seems wrong -- it's at the access site >> where >> you want to disambiguate, which always leads me to think some sigil or >> prefix. >> >> There was an earlier sidetrack which went with ^ as an "unopen" prefix. >> At first, >> my interest was piqued. Naturally, the issue of recursive unopen came >> up... >> >> In response, Gabriel wisely remarked: >> >> "It is remarkable that programming languages have avoided introducing >> explicit weakening (the popping of a symbol out of scope) for now, and >> I think it is a property that should be preserved. We're not yet ready >> to go there." >> >> Good advice when the thread was spinning out of control and probably not >> going >> to settle on anything realistic or favorable. Even though there might be >> merit >> in pursuing fine-grained scope-popping as its own topic. >> >> I think there is a simpler gain to be had from the idea of being able to >> specify >> the path of the current context. "Current context" would need to be >> something sensible, and I'm not sure yet what would be best, as there is a >> related issue I encounter commonly: >> >> A way to specify the path of the current module. >> >> There is no way to do this, right? If I'm in "a.ml", I can't refer to >> A.identifier, and there is no other way to uniquely specify the path to >> what >> *will become* A.identifier? As the bare "identifier" can be shadowed by >> any >> modules opened afterward. Unlike the general "scope-popping", there is >> also >> a common language feature like this: self or this. >> >> I usually want to be explicit with module paths, especially if I am using >> an >> "identifier" which could reasonably be expected to exist now or later in >> the >> other modules being used. I do keep opens to a minimum, but often an >> entire >> expression will be in a local open (to bring in operators), and there, >> again... I would like that clarity, and safeguard against changes which >> might >> happen in the other modules, leading to suprises or need to change *this* >> module for no good reason other than a naming conflict which ideally can >> be >> prepared against. >> >> Has there been any discussion about referring to the local module? My >> guess is >> that it might be a mild enough problem to not warrant any proposed >> solutions. >> But if there are ideas, maybe the same thing or something similar can also >> apply to this problem of "escaping" a local open? They are very similar, >> but >> one is module-scope, while I think the other would be function-scope >> (though >> module-scope might imply the "right thing" anyway)... I'm not certain, as >> haven't been keeping track of the cases I encounter, and others might have >> different use-cases. >> >> >> On Tue, Aug 18, 2015 at 7:00 AM, Gabriel Scherer < >> gabriel.scherer@gmail.com> wrote: >> >>> Note that the dual feature does not exist for variant constructors, >>> because it is easy to define only on the constructor at the toplevel of the >>> pattern, and nested patterns get us in muddy waters. >>> >>> On Tue, Aug 18, 2015 at 2:52 PM, David Allsopp >>> wrote: >>> >>>> Goswin von Brederlow wrote: >>>> > On Fri, Aug 14, 2015 at 01:28:50PM +0200, Drup wrote: >>>> > > >>>> > > >You can't qualifylocal values or values of the surrounding module >>>> so >>>> > > >that is a no go. >>>> > > > >>>> > > >I also often use local open to access records, as in: >>>> > > > >>>> > > >let r = M.({ x = 1; y; z = depth; }) in >>>> > > >>>> > > You can avoid the local open altogether and write it like that: >>>> > > >>>> > > let r = {M. x = 1; y; z = depth } in >>>> > > >>>> > > It's even shorter. >>>> > >>>> > That only works because newer ocaml disambiguises (is that a word?) >>>> record >>>> > So it's implicitly using M.y = y and M.z = depth. >>>> > labels when it determines the record type from the first label, right? >>>> >>>> Only since you ask: "disambiguates" :o) That said, it's quite common to >>>> see words like "disambiguises" being invented by Americans! >>>> >>>> But this isn't related to the disambiguation features of OCaml 4.01+. >>>> Those allow you to write things like: >>>> >>>> type t = {x : int} >>>> type u = {x : int; y : string} >>>> >>>> let foo = {x = 1} >>>> let bar = {x = 42; y = ""} >>>> >>>> This is actually a much older notation added in OCaml 3.08. Prior to >>>> that, if you hadn't opened a module you had to qualify each label: >>>> >>>> {M.x = 1; M.y = y; M.z = depth} >>>> >>>> but this was "silly", since it's not possible to use non-equivalent >>>> module paths for labels, so OCaml 3.08 changed it so that you only needed >>>> to put the module path on one label (and it doesn't have to be the first >>>> one, it's just a bit weird to put it in the middle!). >>>> >>>> OCaml 3.12 added, amongst other record-related goodies, the shorthand >>>> {y} to mean {y = y}. So while you can use local open as you're using it, >>>> you've been able to do it as a totally unambiguous language feature for >>>> quite some time. >>>> >>>> >>>> David >>>> >>>> >>>> -- >>>> Caml-list mailing list. Subscription management and archives: >>>> https://sympa.inria.fr/sympa/arc/caml-list >>>> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners >>>> Bug reports: http://caml.inria.fr/bin/caml-bugs >>>> >>> >>> >> > -- > Simon >