fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
345 stars 21 forks source link

Alow the use of open at type and expression scopes #96

Open baronfel opened 7 years ago

baronfel commented 7 years ago

Submitted by Gauthier Segay on 4/12/2016 12:00:00 AM
79 votes on UserVoice prior to migration

It would be nice to be able to use open in function scope and maybe other places where it currently is illegal.

e.g. see also https://github.com/fsharp/fslang-suggestions/issues/227

Original UserVoice Submission Archived Uservoice Comments

Rickasaurus commented 7 years ago

I would love to have this. In particular it's useful for the pattern of picking a certain set of operator overloads in some tight context.

cartermp commented 4 years ago

This was discussed a bit on Reddit. There's a lot of utility here and it feels fairly natural. One issue is that a motivating use case is being able to remove the full qualifications on collection module functions. However, these are marked as RQA, so "opening List" wouldn't be possible. There would be three outcomes:

  1. Do nothing, RQA is still respected, and this specific use case is something we just accept as not accomplishable
  2. Have type- and function-scoped open declarations bypass RQA
  3. Introduce a "mostly RQA" attribute that allows you to open them only in the type and function scope

I would personally prefer (1).

pblasucci commented 4 years ago

@cartermp for what it’s worth, I also have a strong preference for option one. I’m not keen on weakening the effectiveness RQA.

Tangentially, there was a recent Twitter thread where folks voiced a desire to (somehow... compiler switch?) have RQA be “on by default” (ie: opt-out rather than opt-in). I’m not suggesting we explore that. But, yeah, keeping firm on RQA is important.

Ilazki commented 4 years ago

I'm in favour of option 3. I originally suggested the second option in the reddit discussion, but the explanation of why that might be undesirable made sense so I instead suggested adding an additional relaxed RQA attribute, and I still think it's the best option because of the opt-in nature of it.

My reasoning is that, with the introduction of local opens, the binary "RQA or not" choice currently available will not be nuanced enough. F#'s documentation suggests that RQA be used to avoid name conflicts and improve maintainability; with that in mind, there are cases, like the collection modules, where RQA makes sense now because it blocks conflicts, but in an F# with local opens, the all-or-nothing approach is needlessly inflexible. You won't want to do a full open of List and clobber anything else that has a map or append function, but doing so is perfectly logical in a constrained scope, such as a function you write to manipulate a list, so it would make sense to give the List module the new RelaxedQualifiedAccess (or whatever, I suck at naming things) attribute instead.

Adding a new attribute and moving some of these modules to it would make local opens more widely (and immediately) useful without violating the spirit of the "avoid conflicts and improve maintainability" logic by allowing these limited opens in places it makes sense but is currently not possible, like collections modules. There's a lot of naming overlap in collection modules, so the current RQA restriction makes sense from that perspective, but in an F# that allows limited opens it would be beneficial to allow them to be used there without fully opening them up. It's a perfect example of why you don't want to remove RQA completely but could benefit from a switch to a less restrictive policy.

I’m not keen on weakening the effectiveness RQA.

@pblasucci: Option 3 wouldn't be weakening RQA. Since this new behaviour would be opt-in (via a new attribute), RQA itself would remain unchanged so that nobody would be surprised down the road by their library having looser constraints than they used to have through no action of their own, but libraries that can benefit from the change could be swapped to the new relaxed attribute at their own pace. Existing code not using local opens would see no change, and people not interested in using local opens would see no difference in this relaxed RQA vs. standard RQA so changes to some modules (like List) would not have any effect on them either.

The whole thing becomes opt-in this way: you choose to use local opens, and you choose to allow them. RQA retains its strictness and modules using the relaxed attribute will act like RQA for existing code that isn't using local opens.

amongonz commented 4 years ago

If by local open we mean open List, then I'm for option 1 as I don't think there should be any behavioural difference between a local open and a non-local one. If locally opening a module makes sense, you rely on user discipline to not over-extend the local scope, so you might as well not make it RQA in the first place.

But if an inline open syntax were to be considered too, like OCaml's List.( ... ), that one should probably ignore RQA, as the main use case is removing the boilerplate of module labels in one liners.

Arguably, this is all clearer in OCaml where you can write inner modules for the few functions that make sense to (locally) open and then include them in their parent modules, which you're discouraged to open. If such a pattern were to catch on in F#, we wouldn't need to ignore RQA for any syntax, but since F# lacks include, library authors would have the extra burden of exporting all members a second time.

sheganinans commented 4 years ago

Just my two cents: I like the Rust/Ocaml style local opens, but I also really like RQA.

Yes, it complicates the language, but the code is many times more readable, especially for more complex expressions.

My thinking is that since most of us use some sort of IDE, or vim/emacs+ionide. Most of the disambiguating and questions of "where does this function/type come from?" are answered with a simple hover-over.

492 Seems related.