fslang-suggestions
fslang-suggestions copied to clipboard
global using (open) - similar to C#10
Global using (open)
I propose we add a capability to have a similar functionality of global using in C#, so we can put all open statements in a single file at the top of the project
//GlobalUsings.cs
global using Newtonsoft.Json;
global using ..... ;
....
maybe in F# could look like
//GlobalOpen.fs
global open Newtonsoft.Json
global open ....
...
i have tried the 'AutoOpen" attribute trick on the top of a single file module on top of my project files, but doesnt work unfortunately, the other files/modules still cannot see the already opened namespaces
maybe AutoOpen could be declared on namespaces too as a possible alternative?
Pros and Cons
The advantages of making this adjustment to F# are much less code: more succint --> F# is succint but here C# is more succint atm , so maybe that could be ported.
F# is a Succint, Robust and Performant language
The disadvantages of making this adjustment to F# are .. ? dont know if there is any
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S? don't know
Affidavit (please submit!)
Please tick this by placing a cross in the box:
- [X] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
- [X] I have searched both open and closed suggestions on this site and believe this is not a duplicate
- [X] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.
Please tick all that apply:
- [ ] It is not a breaking change to the F# language design : i supposed it wouldn't be breaking, as a single file at the top maybe still keeps the dependency ordering for open statements before anything else in the .fsproj - please note in the comments below remarks related to possible issues with shadowing and in general the "explicit lang design" of F#.
- [ ] I or my company would be willing to help implement and/or test this
For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.
Related to #1029
The disadvantages of making this adjustment to F# are .. ? dont know if there is any
One I can immediately think of is that F# supports shadowing and C# does not. So it's possible to use this feature (perhaps maliciously?) to hide important namespaces/modules in a component and make it difficult for people to understand why.
The broader one is that F# is a more explicit language than C#, and so there's a question of if this would feel "right" for or "in line with the philosophy" of the language. There's two major places where this isn't true - FSharp.Core is brought in implicitly, and AutoOpen is used in places (often for tiny operator modules for working with a DSL).
I am not a huge fan of globally opening namespaces. I think it was a bad choice even in C#. Sometimes it can already be confusing enough to figure out what namespace/assembly/package something is coming from just by reading source code; the last thing we need to do is make it more ambiguous.
Maybe there is some way to make it less prone to danger of shadowing or malicious purposes, but still achieving a similar functionality? Like at project level?
I see the point now and true is not a really “needed feature”, but maybe go into the “all in one place” and better readability / succinctness for code? Just as that’s stated as the motto of F# so I was wondering if a non dangerous solution might exist.
it should def be something more like opt-in if you like not forcing anyone, maybe should be only invokable within the assembly (internal only, not valid for libraries). This way the user is the one responsible and external shadowing cannot implicitly come from libraries he uses ? Wondering if this would be possible.
Aspnetcore projects would be a real good use case
I will bring some anecdotal experience in favour of this proposal:
- Before moving to F#, I used VB.NET for some years, which has had project-level imports since forever. We'd auto-import
Systemand a few other commonSystem.Xnamespaces, our base utility libraries, and whatever the project mostly worked with - GUI libraries for UI projects, database libraries for , etc.. It never caused any problem.
Most importantly, it made the imports block at the start of a file a short list of actually relevant and readable information, instead of 90% boilerplate with 10% relevant information mixed in. Immediately seeing Imports System.Text.RegularExpressions or Imports System.Reflection front and center, letting you know what you were about to deal with, was much better than having them buried in the middle of 20+ using System.... statements.
(Sidenote: global open System might as well be automatic for me. Who writes System.Guid or System.DateTime?)
- Shadowing at a global level can be very useful to enforce, or at least nudge towards, specific coding practices.
Now I agree that F# should err towards explicitness and readability, so I recommend that global open statements should be allowed at a particular well-defined place in the code and nowhere else. Most likely, just as [<EntryPoint>] needs to be the very last declaration in an executable project, global open should need be the very first ones.
(VB placed global imports in the project file, rather than in the code, which worked well - but F# has scripting and can't assume there will always be a .fsproj)
This would actually be more strict and explicit than current open statements, which can appear at any point in a module/namespace, which is also something I really don't like as you need to check your whole file for leftover imports.
I am a huge fan of succinct code. Implicit global usings/opens for common types are a great way of reducing code. Java had it forever. It has the java.lang package which
Provides classes that are fundamental to the design of the Java programming language.
So when doing System.out.println, we don't have to import java.lang.System. This makes sense for the most common types. No Java programmer has had any issue with this. You are never confused, b/c you quickly get used to it and good IDEs will tell you when an import is not necessary.
I found myself wishing for this after realizing I needed to add open FSharp.Data.UnitSystems.SI.UnitSymbols in order to use standard units of measure. That's a long one to remember and type. The IDE won't auto-suggest it. This makes me reluctant to use them in type definitions, I'd be basically forcing everyone to remember this namespace name anywhere they'll want to use my types.
If we did something to reduce the number of open, I think it would be to allow global open XYZ but require that they be the first things in the compilation order.
I'm not a huge fan, I'm undecided - there is advocacy both ways. I wonder how much people would use them.
An alternative would have something similar to barrel, basically a way to define a group of namespaces in another namespace
I like the idea. Cannot global opes work from the file they are declared down?
I'm not a huge fan, I'm undecided - there is advocacy both ways. I wonder how much people would use them.
A big one would be web frameworks, where a common type is needed in nearly all files. For example, in an elmish/ feliz app, nearly every file will be prefixed with:
open Feliz
open Elmish
I'm a huge fan. At least for my C# projects this feature cleans up my files very nicely. Why shouldn't it for F#?
An alternative would have something similar to barrel, basically a way to define a group of namespaces in another namespace
This sounds like it could be a good compromise. Some kind of mechanism to "bundle" a set of open statements together to be reused explicitly somewhere else.
For example:
// CommonNamespaces.fs
[<ForwardOpens>] // or whatever we would call this attribute / compiler directive / keyword
module CommonNamespaces
open System
open System.Collections.Generic
open System.IO
open System.Text
open System.Text.RegularExpressions
// File2.fs
module Foo.Bar
open CommonNamespaces // imports all of those namespaces
let f x = File.ReadAllText(x)
If it were an attribute like this, you could presumably add an [<AutoOpen>] to CommonNamespaces to get that same global behavior, if you wanted the implicitness.
However I mainly meant to spark more conversation about that type of feature (naming a bundle of namespaces) rather than propose a specific syntax. There may certainly be a better syntax than the one I sketched out - I'd love to hear ideas.