Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider documenting why certain config options exist #2979

Open
brianrourkeboll opened this issue Nov 8, 2023 · 4 comments
Open

Consider documenting why certain config options exist #2979

brianrourkeboll opened this issue Nov 8, 2023 · 4 comments

Comments

@brianrourkeboll
Copy link

brianrourkeboll commented Nov 8, 2023

I think it might be worth augmenting the existing documentation (e.g., here) with some of the following information, where appropriate:

  1. Why various configuration options exist.
  2. Why the user might choose one setting for a given option over another (there is some of this for, e.g., fsharp_multiline_bracket_style = stroustrup).
  3. Whether there are groups of related options that it might make sense to set consistently with each other, like is already done for G-Research settings—but also why you might do that.

I could see it argued that someone could just document such information in their own style guide instead, but there are some settings that are currently supported for which the existing style guides offer rules but not explanations, and there are some widespread styles for which there is no official, published guide.

I think adding this explanatory information to the Fantomas docs would be especially useful for people coming to F# from C-family languages. In my own experience, I have seen many newcomers perplexed as to why one might use or omit a space in various contexts, for example. Discrepancies between the tooling in Ionide, Rider, and Visual Studio, and the changes to the default formatting used by the compiler (and in documentation) have also contributed to the confusion, especially when newcomers start working in big, long-lived codebases, where significant amounts of old code were written before some of the more recent formatting styles became popular in F#.

Examples

These are some (surely incomplete) examples of the kinds of implicit motivation/historical background for some settings and grouping of settings that it might be worth documenting. They mostly represent my own poorly-phrased understanding or reasoning, so I may be wrong about some things. And there are of course surely other rationales and histories out there for many of the settings, some of which may or may not be worth mentioning. Either way, there is probably someone out there better versed in the lore here than I am—maybe there are blog posts or other writing or documentation that we could consult.

ML style

This seemed like one of the most common styles when I started learning F# a number of years ago, including in the compiler and in the VS tooling.

I think it was mostly inherited from OCaml:

  • Compact brackets are, well, compact, and they can make dense code easier to read (by virtue of there being less of it).
  • A space between a function name and a parenthesized parameter keeps consistency between single-parameter functions and curried functions with multiple arguments, e.g., f (x : int) and f (x : int) (y : int).
  • A space between a member name and a parenthesized parameter maintains consistency between functions and methods, including the type system's treatment of f (x, y) and o.M (x, y) as functions taking a single tupled argument.
  • A space before a class constructor maintains symmetry between declaring constructors and using them as functions, where the type system otherwise usually treats them like any other curried function.
  • A space between a function, method, or constructor name and a parenthesized argument to which it is being applied enables consistency between single-parameter functions and curried functions with multiple arguments, and symmetry with their declarations.
  • A space before a colon enables consistency in cases like let f (x : int) : int = x, etc. Perhaps owes its origin to OCaml's French heritage, since the convention when writing French is to use a space before colons (and a variety of other punctuation).
fsharp_multiline_bracket_style = compact
fsharp_space_before_parameter = true
fsharp_space_before_lowercase_invocation = true
fsharp_space_before_uppercase_invocation = true
fsharp_space_before_member = true
fsharp_space_before_class_constructor = true
fsharp_space_before_colon = true

Interop/".NET"/C#/TypeScript style

  • Stroustrup-style brackets are reasonably common in C# and are by far the commonest in TypeScript (and JavaScript, Java, etc.).
  • No space before parameters or constructors makes single-argument (including tupled) methods, functions, and constructors look more similar to method or constructor calls in C-family languages. It also makes it easier to use "fluent" method chains, especially when consuming .NET APIs written primarily with C# in mind.
  • No space before colons makes type annotations look more similar to those in TypeScript, Rust, Swift, etc.
fsharp_multiline_bracket_style = stroustrup
fsharp_space_before_parameter = false
fsharp_space_before_lowercase_invocation = false
fsharp_space_before_uppercase_invocation = false
fsharp_space_before_class_constructor = false
fsharp_space_before_member = false
fsharp_space_before_colon = false

Haskell style

This is probably common in OCaml and many other languages, too.

// ML style, plus these:
fsharp_space_before_semicolon = true
fsharp_space_around_delimiter = true

Diff minimization

  • Aligned brackets obviate the need to realign the trailing bracket when editing the bracket contents.
  • A newline before the closing parenthesis in a multiline lambda obviates the need to realign the closing parenthesis when editing the lambda body, especially if there are nested parenthesized expressions.
  • Always adding a bar before union case declarations obviates the need to add a bar if a later change would require it (and makes the declaration of the first case consistent with following cases).
  • No space for invocations, constructions, or deconstructions minimizes the diff if a fluent call is added later, and maximizes consistency between calls in fluent chains and standalone calls.
fsharp_multiline_bracket_style = aligned
fsharp_multi_line_lambda_closing_newline = true
fsharp_bar_before_discriminated_union_declaration = true
fsharp_space_before_parameter = false
fsharp_space_before_lowercase_invocation = false
fsharp_space_before_uppercase_invocation = false
fsharp_space_before_class_constructor = false
fsharp_space_before_member = false

I'd be willing to open a PR for this.

@dawedawe
Copy link
Member

dawedawe commented Nov 9, 2023

Hey @brianrourkeboll ,
as we aren't super sure about this one yet and we don't want you to waste your time, maybe we should have a call before you get started on a potential PR.
How about next week? Are you on discord or slack?

@brianrourkeboll
Copy link
Author

Sure, no problem. I am on the F# Discord.

I'm also open to the possibility that the Fantomas docs aren't the best place for something like this, although it does seem like something that would be useful to have in some discoverable, well-known place.

@dawedawe
Copy link
Member

Great, we'll get in touch with you.

@josh-degraw
Copy link
Contributor

I think this would be super useful as a blog post at the very least. I actually especially like the examples with "recipes" of common styles in that context.

I also do think it's worth mentioning that having "recipes" like this technically contradicts the official recommendation in the docs to just stick with the defaults, although I personally think we may want to update at least part of that section:

To strengthen the message of unity we advice that you do not change the default settings. The out-of-the-box experience should be a result of what the brightest minds of the community came up with. If you are new to the F# language, this is what you want.

For example, there are now 3 officially recommended ways to format record types, so I don't know if it's accurate to say that the default settings are always the "best".

Regardless, ihmo having some minimal explanation as to why a given setting exists can certainly be useful even without being phrased as a recommendation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants