Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
Top-level type declarations
I propose we add top-level type declarations.
namespace Namespace.Namespace // :) module Nested = do ()
module Namespace.Module // :) module Nested = do ()
type Namespace.Interface // :( abstract Method : unit -> unit
type [<Struct>] Namespace.Structure // :( member _.Method = ()
type Namespace.Class(parameter) // :( member _.Method = ()
Large classes that are analogous to large modules with a constructor should receive the same syntactic sugar as modules.
The existing way of approaching this problem in F# is having an extra indentation level spanning almost the entire file.
Pros and Cons
The advantages of making this adjustment to F# are
The disadvantages of making this adjustment to F# are none that I can think of.
Estimated cost (XS, S, M, L, XL, XXL): S
Related suggestions: None
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
I don't see any real advantage. This change leads to a code base where each class has it's own file. This is C#'s style of OOP programming. F# discourages this by design.
Large classes are a code smell. They get hard to reason about, because mutable state could be modified all over the place.
I don't really see the problem that you are trying to solve, because top-level type declarations are already possible:
The only difference seems to be that in your example code, you would like to magically add namespaces. But that is not what the syntax for modules does. It creates nested modules:
module X.Y.Z // declares namespace X.Y and inside it, module Z module Foo = () // creates module Z.Foo (nested module) inside namespace X.Y
This syntax only works with the "namespace + module" statement at the top of your file. Inside the module you can place let bindings, nested modules and types.
If we were to allow this syntax for types, it should form the same analogy:
type X.Y.Z // declares namespace X.Y and inside it, type Z type Foo() = ... // creates type Z.Foo (nested type) inside namespace X.Y
However, nested types are not allowed, so I think it is reasonable that we don't allow this syntax until nested types are allowed.
But this is not analogous. Analogous would be that under
Not quite, as I showed you, you can have multiple types at top-level. However, you cannot nest them. And members must be indented to show that they belong to that type. But if you don't like far indentation, you can set it to one space (but I doubt that makes your code more readable).
Perhaps there's something to say for not having to indent members of a type, just like you don't have to indent let-bindings of a module. But I believe common code practice is to always indent what belongs to a certain module, as that aids in readability. Since indenting is structural and fundamental to F#, I think we should keep this clarity.
Moreover, if you have multiple types (common) and multiple modules (less common) in a single file, if you weren't indenting members, the only way to find out what a member belongs to is to scroll back and look very closely at
type Translations(language) = let case chinese english = match language with Language.Chinese -> chinese | Language.English -> English member _.``Loading…`` = case "載入中⋯⋯" "Loading…" member _.``Network is unstable, please check your Internet connection.`` = case "網路連線不正常請確認" "Network is unstable, please check your Internet connection." member _.``Quit`` = case "離開" "Quit" member _.``Unable to login automatically, please confirm.`` = case "無法自動登入請確認" "Unable to login automatically, please confirm." member _.``Acknowledge`` = case "確認" "Acknowledge" member _.``User ID must be between 3 and 20 characters.`` = case "帳號長度必須在3~20個字元之內" "User ID must be between 3 and 20 characters." member _.``Password must be between 4 and 12 characters.`` = case "密碼長度必須在4~12個字元之內" "Password must be between 4 and 12 characters." member _.``Passwords do not match!`` = case "密碼輸入內容不一致！" "Passwords do not match!" member _.``Incorrect date format(s).`` = case "日期輸入格式錯誤" "Incorrect date format(s)." member _.``Set successfully!`` = case "設定成功！" "Set successfully!" member _.``Timeout!`` = case "設定逾時！" "Timeout!" //...
If the module with functions approach is to be used, there would be annoying additional parameter passing and brackets needed whenever the property is called.
I share @abelbraaksma's confusion with this point:
Since as @abelbraaksma points out, you don't need to indent types in a file:
namespace MyNameSpace type C1() = class end type C2() = class end type C3() = class end
Only if you contain them in a module within that file is it necessary to indent.
Completely OT: In the specific case of localization I would recommend to stick with the standard approach and use .resx-files (there are lots of tools to facilitate the translation process) in combination with the ResX type provider, which allows you to use .resx-files in a type safe manner.
Why? Your code suggests a script that does translations. While I disagree to the approach (best practice is using resx, and the drawbacks can be catered for), assuming the language is set once anyway, you can write the same code in a parens-less way in a module (but I don't know the rest of your code, so take this advice as is):
module Translations = let case chinese english = match Glob.Language with Language.Chinese -> chinese | Language.English -> English // no parens, just evaluated once on first use let ``Loading…`` = case "載入中⋯⋯" "Loading…" //...
However, it seems to me that your code is clearer with the indentation either way. I wouldn't remove the indentation for the
As an aside, if you do decide to use resx, it generally works easier from a C# project, imo. And you can use tools like these to get better type safety: https://marketplace.visualstudio.com/items?itemName=Paruz.ExtendedStronglyTypedResourceGenerator (may need to recompile it to get it to work in VS 2019).
But I'm digressing. It wasn't clear from your original suggestion that you wanted to treat indentation more lenient for members only, as you were talking about namespaces etc. I don't think that's a good idea per my comments before, and may not even be possible without introducing ambiguities in the parser.
Well, in that case there are other coding patterns, not in the least the resx approach with type provider mentioned earlier, or an event-based, or global mutable setting for the language paired with unit functions.
Using in-code strings like in your example is hard to maintain (consider adding a language, ouch), but if you have a large code base, it can be equally hard to refactor all of it.
Am I right to a assume that your original request was mainly to make this large type more easily maintainable?