Skip to content

F# style guidelines: Recommend non-vanity indentation #21487

@cmeeren

Description

@cmeeren

The following is a request to change the F# style guidelines.

In section Formatting white space, subsection Place parameters on a new line for long definitions, the style guide says:

If you have a very long function definition, place the parameters on new lines and indent them to match the indentation level of the subsequent parameter.

module M =
    let LongFunctionWithLotsOfParameters(aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
                                        (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
                                        (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
                                        =
        // ... the body of the method follows

As mentioned in fsprojects/fantomas#657, this kind of indentation (AFAIK called "vanity indentation") has several drawbacks:

  • Important code moved far to the right
  • There is little space left for the actual code
  • Refactoring names breaks the alignment (even if using tooling like Fantomas to auto-format code, the tooling may need to be explicitly invoked on all changed files, and AFAIK renames that extend names may cause compiler warnings)
  • There is an unseemly gap on the left-hand side (personal opinion)

I would format the above example like this:

module M =
    let LongFunctionWithLotsOfParameters
        (aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
        (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
        (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
        =
        // ... the body of the method follows

This has none of the mentioned drawbacks.

As seen in the linked section, this also goes for the recommendations for members and constructors.

Another section which has the same problem is Formatting copy-and-update record expressions, which has this example:

let newState =
    {
        state with
            F = Some {
                    F1 = 0
                    F2 = ""
                }
    }

Notice how the indentation of the F value is sensitive to the name of F:

let newState =
    {
        state with
            AGorillaHoldingTheBananaAndTheEntireJungle = Some {
                                                             F1 = 0
                                                             F2 = ""
                                                         }
    }

A better approach would be:

let newState =
    {
        state with
            AGorillaHoldingTheBananaAndTheEntireJungle =
                Some {
                    F1 = 0
                    F2 = ""                    
                }
    }

Conversely, in the section Formatting discriminated unions (unrelated to explicit whitespace recommendations), my preferred approach is demonstrated:

let tree1 =
    BinaryNode
        (BinaryNode(BinaryValue 1, BinaryValue 2),
         BinaryNode(BinaryValue 3, BinaryValue 4))

Furthermore, in the section Formatting pipeline operators, the non-vanity indentation kind is also shown (though vanity indentation would have |> aligned three more spaces to the right):

// Preferred approach
let methods2 =
    System.AppDomain.CurrentDomain.GetAssemblies()
    |> List.ofArray
    |> List.map (fun assm -> assm.GetTypes())
    |> Array.concat
    |> List.ofArray
    |> List.map (fun t -> t.GetMethods())
    |> Array.concat

// Not OK
let methods2 = System.AppDomain.CurrentDomain.GetAssemblies()
            |> List.ofArray
            |> List.map (fun assm -> assm.GetTypes())
            |> Array.concat
            |> List.ofArray
            |> List.map (fun t -> t.GetMethods())
            |> Array.concat

In light of these counter-examples, one could argue that changing recommending non-vanity indentation would make the style guide more consistent.


To fix this issue, I suggest the following:

If this is acceptable, I can help create a PR if desired, but I have no personal need to put my name on this change and really just want it changed regardless of who does the work.

/cc @nojaf @knocte @lydell @pragmatrix @cartermp

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions