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 <simon.cruanes.2007@m4x.org> 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 <dra-news@metastack.com> 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