A lot of great ideas here. I want to comment on many of them, so please bear with me while I summarize some of the points in the process. Hopefully this will also help anybody new to the discussion, which has become extremely long.
- Local opens are somewhat of an anti-pattern in OCaml, because they're usually used in places where you have the same names defined in multiple modules:
(* both M and N are in other files, and they define (+). M also defines (*) *)
M.(let x = foo + bar in
N.(y + z * x))
The first + uses M's implementation and the second uses N's implementation.
The problem is, if you ever change one of the modules to include another function that wasn't expected originally (for example, N now includes *), you now have subtle bugs breaking your code in completely separate files from the ones you were editing, and the type system can't necessarily do anything to catch these errors.
- Similar languages to OCaml (such as Haskell) outlaw having the same name defined twice in the same scope. OCaml only issues a warning. IMO, this warning should be turned into an error. However, these other languages also provide a way to use the same name in the same scope by having type-based dispatch or disambiguation. For example, haskell has typeclasses. OCaml currently doesn't have this. I think this shows that Modular Implicits aren't just a nice-to-have feature -- we really need them to be able to get to do the kinds of things other languages do safely.
- I personally don't think the splitting of the warning between alphanumerics and operators is that helpful. This isn't a warning you should ever turn off IMO, regardless of the domain.
- The destructive versions of local opens which disable these warnings are even worse anti-patterns, since they're just a huge recipe for bugs.
- In the absence of Modular Implicits, as Petter mentioned, it would be nice to have the warnings/potential errors only generated when the same names have the same types. In case of different types with the same name, the type system should take care of any issues. For example, if M's (*) has a different type than N's (*), there's no problem per se since we'll get a type error. I think this is definitely something worth pursuing.
- In general, it would be nice not to have to open all these local scopes, and rather, as Simon mentioned, to reference operators directly when needed (as in, M.+). The problem is that these cannot be used infix in the code due to parser reasons. I think that while we're looking for a parsing solution for this, it might be better to also look for a parsing solution for turning any name into an infix function, and simply apply that. Haskell uses backtick (` as in x `mod` y), which we already use for polymorphic variants. Can we think of another symbol with which to do this? We could then apply it to M.+ as well.
- Another good point is that it would be nice to limit our imports of a module in the same file in which it is used. The issue is that module syntax is too verbose, due to the fact that it requires types. However, our module type signatures could possibly drop the type definitions, relying on imported type definitions instead. Something like the following would be nice:
module M : sig val (+) val (-) val mult end = LongModuleName
Note that this is similar to haskell's qualified import statement, but is more powerful since it creates a local module. This is also easy to parse since val is a keyword. The dropped types would form holes in the signature, which must be made available in the referenced module. When wanting to do multi-level local opens, rather than opening modules blindly, you'd first create smaller submodules locally, and then only open those locally. This would be much safer, and is in fact much closer to the original point of having multiple possible type signature 'views' into a module implementation (which is nice in theory but is not used much except for functors and artificial illustrations of OCaml's abilities).
- Another point that was brought up was that it would be nice to access the local scope of the current module, which I think is extremely useful. How about _.foo as a possible syntax?
-Yotam