From 6f6207e4bac6f48b33cee50e4de7b5501888649a Mon Sep 17 00:00:00 2001 From: Don Syme Date: Mon, 6 Sep 2021 13:59:36 +0100 Subject: [PATCH 1/9] Revise F# formatting guide --- docs/fsharp/style-guide/formatting.md | 1723 +++++++++++++------------ 1 file changed, 897 insertions(+), 826 deletions(-) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 433bca3ce73da..6a6fba3063e3d 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -8,95 +8,43 @@ ms.date: 08/31/2020 This article offers guidelines for how to format your code so that your F# code is: * More legible -* In accordance with conventions applied by formatting tools in Visual Studio and other editors +* In accordance with conventions applied by formatting tools in Visual Studio Code and other editors * Similar to other code online These guidelines are based on [A comprehensive guide to F# Formatting Conventions](https://github.com/dungpa/fantomas/blob/master/docs/FormattingConventions.md) by [Anh-Dung Phan](https://github.com/dungpa). -## General rules for indentation +See also [Coding conventions](conventions/) and [Component design guidelines](component-design-guidelines), which also covers naming conventions. -F# uses significant white space by default. The following guidelines are intended to provide guidance as to how to juggle some challenges this can impose. +## General rules for formatting -### Using spaces +F# uses significant white space by default and is white space sensitive. +The following guidelines are intended to provide guidance as to how to juggle some challenges this can impose. -When indentation is required, you must use spaces, not tabs. At least one space is required. Your organization can create coding standards to specify the number of spaces to use for indentation; two, three, or four spaces of indentation at each level where indentation occurs is typical. +### Use spaces not tabs -**We recommend four spaces per indentation.** - -That said, indentation of programs is a subjective matter. Variations are OK, but the first rule you should follow is *consistency of indentation*. Choose a generally accepted style of indentation and use it systematically throughout your codebase. - -## Formatting white space - -F# is white space sensitive. Although most semantics from white space are covered by proper indentation, there are some other things to consider. - -### Formatting operators in arithmetic expressions - -Always use white space around binary arithmetic expressions: - -```fsharp -let subtractThenAdd x = x - 1 + 3 -``` - -Unary `-` operators should always be immediately followed by the value they are negating: - -```fsharp -// OK -let negate x = -x - -// Bad -let negateBad x = - x -``` - -Adding a white-space character after the `-` operator can lead to confusion for others. - -In summary, it's important to always: - -* Surround binary operators with white space -* Never have trailing white space after a unary operator - -The binary arithmetic operator guideline is especially important. Failing to surround a binary `-` operator, when combined with certain formatting choices, could lead to interpreting it as a unary `-`. - -### Surround a custom operator definition with white space - -Always use white space to surround an operator definition: - -```fsharp -// OK -let ( !> ) x f = f x - -// Bad -let (!>) x f = f x -``` - -For any custom operator that starts with `*` and that has more than one character, you need to add a white space to the beginning of the definition to avoid a compiler ambiguity. Because of this, we recommend that you simply surround the definitions of all operators with a single white-space character. +When indentation is required, you must use spaces, not tabs. F# code does not use tabs and the compiler will give an error if a tab character is encountered outside +a string literal or comment. -### Surround function parameter arrows with white space +### Use consistent indentation -When defining the signature of a function, use white space around the `->` symbol: - -```fsharp -// OK -type MyFun = int -> int -> string +When indenting, at least one space is required. Your organization can create coding standards to specify the number of spaces to use for indentation; two, three, or four spaces of indentation at each level where indentation occurs is typical. -// Bad -type MyFunBad = int->int->string -``` +**We recommend four spaces per indentation.** -### Surround function arguments with white space +That said, indentation of programs is a subjective matter. Variations are OK, but the first rule you should follow is *consistency of indentation*. Choose a generally accepted style of indentation and use it systematically throughout your codebase. -When defining a function, use white space around each argument. +### Use an automatic code formatter -```fsharp -// OK -let myFun (a: decimal) b c = a + b + c +The [Fantomas code formatter](https://github.com/fsprojects/fantomas/#fantomas) is the F# community standard tool for automatic code formatting. The default +settings correspond to this style guide. -// Bad -let myFunBad (a:decimal)(b)c = a + b + c -``` +We strongly recommend the use of this code formatter. Within F# teams, code formatting specifications should be agreed and codified in terms +of a agreed settings for the code formatter. If the code formatter doesn't implement the setting you require, contribute a new +setting to the project. -### Avoid name-sensitive alignments +### Avoid formatting that is sensitive to name length -In general, seek to avoid indentation and alignment that is sensitive to naming: +Seek to avoid indentation and alignment that is sensitive to naming: ```fsharp // OK @@ -104,7 +52,6 @@ let myLongValueName = someExpression |> anotherExpression - // Bad let myLongValueName = someExpression |> anotherExpression @@ -116,378 +63,362 @@ This is sometimes called “vanity alignment” or “vanity indentation”. The * There is less width left for the actual code * Renaming can break the alignment -Do the same for `do`/`do!` in order to keep the indentation consistent with `let`/`let!`. Here is an example using `do` in a class: +### Avoid extraneous white space + +Avoid extraneous white space in F# code, except where described in this style guide. ```fsharp // OK -type Foo () = - let foo = - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog - do - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog +spam (ham.[1]) -// Bad - notice the "do" expression is indented one space less than the `let` expression -type Foo () = - let foo = - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog - do fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog +// Not OK +spam ( ham.[ 1 ] ) ``` -Here is an example with `do!` using 2 spaces of indentation (because with `do!` there is coincidentally no difference between the approaches when using 4 spaces of indentation): +## Formatting comments + +Prefer multiple double-slash comments over block comments. ```fsharp -// OK -async { - let! foo = - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog - do! - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog -} +// Prefer this style of comments when you want +// to express written ideas on multiple lines. -// Bad - notice the "do!" expression is indented two spaces more than the `let!` expression -async { - let! foo = - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog - do! fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog -} +(* + Block comments can be used, but use sparingly. + They are useful when eliding code sections. +*) ``` -### Place parameters on a new line for long definitions - -If you have a long function definition, place the parameters on new lines and indent them to match the indentation level of the subsequent parameter. +Comments should capitalize the first letter and be well-formed phrases or sentences. ```fsharp -module M = - let longFunctionWithLotsOfParameters - (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) - (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse) - (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse) - = - // ... the body of the method follows - - let longFunctionWithLotsOfParametersAndReturnType - (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) - (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse) - (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse) - : ReturnType = - // ... the body of the method follows - - let longFunctionWithLongTupleParameter - ( - aVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse - ) = - // ... the body of the method follows +// A good comment +let f x = x + 1 // Increment by one. - let longFunctionWithLongTupleParameterAndReturnType - ( - aVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse - ) : ReturnType = - // ... the body of the method follows +// A poor comment +let f x = x + 1 // plus one ``` -This also applies to members, constructors, and parameters using tuples: +For formatting XML doc comments, see "Formatting declarations" below. -```fsharp -type TM() = - member _.LongMethodWithLotsOfParameters - ( - aVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse - ) = - // ... the body of the method +## Formatting expressions -type TC - ( - aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse, - aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse, - aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse - ) = - // ... the body of the class follows -``` +### Formatting string expressions -If the parameters are currified, place the `=` character along with any return type on a new line: +String literals and interpolated strings can just be left on a single line, regardless of how long the line is. ```fsharp -type C() = - member _.LongMethodWithLotsOfCurrifiedParamsAndReturnType - (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) - (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse) - (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse) - : ReturnType = - // ... the body of the method - member _.LongMethodWithLotsOfCurrifiedParams - (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) - (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse) - (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse) - = - // ... the body of the method +let serviceStorageConnection = + $"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}" ``` -This is a way to avoid too long lines (in case return type might have long name) and have less line-damage when adding parameters. +Multi-line interpolated expressions are discouraged. Instead, bind the expression result to a value and use that in the interpolated string. -### Type annotations +## Formatting tuple expressions -#### Right-pad value and function argument type annotations +A tuple instantiation should be parenthesized, and the delimiting commas within it should be followed by a single space, for example: `(1, 2)`, `(x, y, z)`. -When defining values or arguments with type annotations, use white space after the `:` symbol, but not before: +It is commonly accepted to omit parentheses in pattern matching of tuples: ```fsharp -// OK -let complexFunction (a: int) (b: int) c = a + b + c -let expensiveToCompute: int = 0 // Type annotation for let-bound value - -type C() = - member _.Property: int = 1 +let (x, y) = z // Destructuring +let x, y = z // OK -// Bad -let complexFunctionBad (a :int) (b :int) (c:int) = a + b + c -let expensiveToComputeBad1:int = 1 -let expensiveToComputeBad2 :int = 2 +// OK +match x, y with +| 1, _ -> 0 +| x, 1 -> 0 +| x, y -> 1 ``` -#### Surround return type annotations with white space - -In function or member return type annotations, use white space before and after the `:` symbol: +It is also commonly accepted to omit parentheses if the tuple is the return value of a function: ```fsharp // OK -let myFun (a: decimal) b c : decimal = a + b + c // Type annotation for the return type of a function -let anotherFun (arg: int) : unit = () // Type annotation for return type of a function -type C() = - member _.SomeMethod(x: int) : int = 1 // Type annotation for return type of a member - -// Bad -let myFunBad (a: decimal) b c:decimal = a + b + c -let anotherFunBad (arg: int): unit = () -type C() = - member _.SomeMethod(x: int): int = 1 +let update model msg = + match msg with + | 1 -> model + 1, [] + | _ -> model, [ msg ] ``` -### Formatting bindings +In summary, prefer parenthesized tuple instantiations, but when using tuples for pattern matching or a return value, it is considered fine to avoid parentheses. -In all cases, the right-hand side of a binding either all goes on one line, or (if it's too long) goes on a new line indented one scope. +## Formatting application expressions -For example, the following are non-compliant: +When formatting a function or method application, arguments are provided on the same line when line-width allows: ```fsharp -let a = """ -foobar, long string -""" +// OK +someFunction1 x.IngredientName x.Quantity +``` -type File = - member this.SaveAsync(path: string) : Async = async { - // IO operation - return () - } +Parentheses should be omitted unless arguments require them: -let c = { - Name = "Bilbo" - Age = 111 - Region = "The Shire" -} +```fsharp +// OK +someFunction1 x.IngredientName -let d = while f do - printfn "%A" x +// Not OK +someFunction1(x.IngredientName) ``` -The following are compliant: +You may need to pass arguments to a function on a new line, as a matter of readability or because the list of arguments or the argument names are too long. In that case, indent one level: ```fsharp -let a = - """ -foobar, long string -""" +// OK +someFunction2 + x.IngredientName x.Quantity -type File = - member this.SaveAsync(path: string) : Async = - async { - // IO operation - return () - } +// OK +someFunction3 + x.IngredientName1 x.Quantity2 + x.IngredientName2 x.Quantity2 -let c = - { Name = "Bilbo" - Age = 111 - Region = "The Shire" } +// OK +someFunction4 + x.IngredientName1 + x.Quantity2 + x.IngredientName2 + x.Quantity2 -let d = - while f do - printfn "%A" x +// OK +someFunction5 + (convertVolumeToLiter x) + (convertVolumeUSPint x) + (convertVolumeImperialPint x) ``` -## Formatting blank lines - -* Separate top-level function and class definitions with two blank lines. -* Method definitions inside a class are separated by a single blank line. -* Extra blank lines may be used (sparingly) to separate groups of related functions. Blank lines may be omitted between a bunch of related one-liners (for example, a set of dummy implementations). -* Use blank lines in functions, sparingly, to indicate logical sections. - -## Formatting comments - -Generally prefer multiple double-slash comments over ML-style block comments. +When the function takes a single multi-line tupled argument, place each argument on a new line: ```fsharp -// Prefer this style of comments when you want -// to express written ideas on multiple lines. +// OK +someTupledFunction( + 478815516, + "A very long string making all of this multi-line", + 1515, + false +) -(* - ML-style comments are fine, but not a .NET-ism. - They are useful when needing to modify multi-line comments, though. -*) +// OK +someTupledFunction + (478815516, + "A very long string making all of this multi-line", + 1515, + false) +) ``` -Inline comments should capitalize the first letter. +If argument expressions are short, separate arguments with spaces and keep it in one line. ```fsharp -let f x = x + 1 // Increment by one. -``` - -## Formatting string literals and interpolated strings +let person = new Person(a1, a2) -String literals and interpolated strings can just be left on a single line, regardless of how long the line is. +let myRegexMatch = Regex.Match(input, regex) -```fsharp -let serviceStorageConnection = - $"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}" +let untypedRes = checker.ParseFile(file, source, opts) ``` -Multi-line interpolated expressions are strongly discouraged. Instead, bind the expression result to a value and use that in the interpolated string. +If argument expressions are long, use newlines and indent one level, rather than indenting to the left-parenthesis. -## Naming conventions - -### Use camelCase for class-bound, expression-bound, and pattern-bound values and functions +```fsharp +// OK +let person = + new Person( + argument1, + argument2 + ) -It is common and accepted F# style to use camelCase for all names bound as local variables or in pattern matches and function definitions. +// OK +let myRegexMatch = + Regex.Match + ("my longer input string with some interesting content in it", + "myRegexPattern") -```fsharp // OK -let addIAndJ i j = i + j +let untypedRes = + checker.ParseFile( + fileName, + sourceText, + parsingOptionsWithDefines + ) -// Bad -let addIAndJ I J = I+J +// Not OK +let person = + new Person(argument1, + argument2) -// Bad -let AddIAndJ i j = i + j +// Not OK +let untypedRes = + checker.ParseFile(fileName, + sourceText, + parsingOptionsWithDefines) ``` -Locally bound functions in classes should also use camelCase. +The same rules apply even if there is only a single multi-line argument. ```fsharp -type MyClass() = +let poemBuilder = StringBuilder() +poemBuilder.AppendLine( + """ +The last train is nearly due +The Underground is closing soon +And in the dark, deserted station +Restless in anticipation +A man waits in the shadows + """ +) - let doSomething () = +Option.traverse( + create + >> Result.setError [ invalidHeader "Content-Checksum" ] +) +``` - let firstResult = ... +### Formatting pipeline expressions - let secondResult = ... +When using multiple lines, pipeline `|>` operators should go underneath the expressions they operate on. - member x.Result = doSomething() +```fsharp +// 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 + +// Not OK either +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 ``` -### Use camelCase for module-bound public functions +### Formatting lambda expressions -When a module-bound function is part of a public API, it should use camelCase: +When a lambda expression is used as an argument in a multi-line expression, and is followed by other arguments, +place the body of a lambda expression on a new line, indented by one level: ```fsharp -module MyAPI = - let publicFunctionOne param1 param2 param2 = ... - - let publicFunctionTwo param1 param2 param3 = ... +let printListWithOffset a list1 = + List.iter + (fun elem -> + printfn $"A very long line to format the value: %d{a + elem}") + list1 ``` -### Use camelCase for internal and private module-bound values and functions +If the lambda argument is the last argument in a function application, place all arguments until the arrow on the same line. -Use camelCase for private module-bound values, including the following: +```fsharp +Target.create "Build" (fun ctx -> + // code + // here + ()) -* Ad hoc functions in scripts +let printListWithOffsetPiped a list1 = + list1 + |> List.iter (fun elem -> + printfn $"A very long line to format the value: %d{a + elem}") +``` -* Values making up the internal implementation of a module or type +Treat match lambda's in a similar fashion. ```fsharp -let emailMyBossTheLatestResults = - ... +functionName arg1 arg2 arg3 (function + | Choice1of2 x -> 1 + | Choice2of2 y -> 2) ``` -### Use camelCase for parameters - -All parameters should use camelCase in accordance with .NET naming conventions. +When there are many leading or multi-line arguments before the lambda indent all arguments with one level. ```fsharp -module MyModule = - let myFunction paramOne paramTwo = ... +functionName + arg1 + arg2 + arg3 + (fun arg4 -> + bodyExpr) -type MyClass() = - member this.MyMethod(paramOne, paramTwo) = ... +functionName + arg1 + arg2 + arg3 + (function + | Choice1of2 x -> 1 + | Choice2of2 y -> 2) ``` -### Use PascalCase for modules +If the body of a lambda expression is multiple lines long, you should consider refactoring it into a locally-scoped function. -All modules (top-level, internal, private, nested) should use PascalCase. +When pipelines include lambda expressions, each lambda expression is typically the last argument at each stage of the pipeline: ```fsharp -module MyTopLevelModule - -module Helpers = - module private SuperHelpers = - ... +// With 4 spaces indentation +let printListWithOffsetPiped list1 = + list1 + |> List.map (fun elem -> elem + 1) + |> List.iter (fun elem -> + // one indent starting from the pipe + printfn $"A very long line to format the value: %d{elem}") - ... +// With 2 spaces indentation +let printListWithOffsetPiped list1 = + list1 + |> List.map (fun elem -> elem + 1) + |> List.iter (fun elem -> + // one indent starting from the pipe + printfn $"A very long line to format the value: %d{elem}") ``` -### Use PascalCase for type declarations, members, and labels +### Formatting arithmetic and binary expressions -Classes, interfaces, structs, enumerations, delegates, records, and discriminated unions should all be named with PascalCase. Members within types and labels for records and discriminated unions should also use PascalCase. +Always use white space around binary arithmetic expressions: ```fsharp -type IMyInterface = - abstract Something: int +let subtractThenAdd x = x - 1 + 3 +``` -type MyClass() = - member this.MyMethod(x, y) = x + y +Failing to surround a binary `-` operator, when combined with certain formatting choices, could lead to interpreting it as a unary `-`. +Unary `-` operators should always be immediately followed by the value they are negating: -type MyRecord = { IntVal: int; StringVal: string } +```fsharp +// OK +let negate x = -x -type SchoolPerson = - | Professor - | Student - | Advisor - | Administrator +// Bad +let negateBad x = - x ``` -### Use PascalCase for constructs intrinsic to .NET - -Namespaces, exceptions, events, and project/`.dll` names should also use PascalCase. Not only does this make consumption from other .NET languages feel more natural to consumers, it's also consistent with .NET naming conventions that you are likely to encounter. - -### Avoid underscores in names +Adding a white-space character after the `-` operator can lead to confusion for others. -Historically, some F# libraries have used underscores in names. However, this is no longer widely accepted, partly because it clashes with .NET naming conventions. That said, some F# programmers use underscores heavily, partly for historical reasons, and tolerance and respect is important. However, the style is often disliked by others who have a choice about whether to use it. +Separate binary operators by spaces. Infix expressions are OK to lineup on same column: -One exception includes interoperating with native components, where underscores are common. +```fsharp +let function1 () = + acc + + (someFunction + x.IngredientName x.Quantity) -### Use standard F# operators +let function1 arg1 arg2 arg3 arg4 = + arg1 + arg2 + + arg3 + arg4 +``` -The following operators are defined in the F# standard library and should be used instead of defining equivalents. Using these operators is recommended as it tends to make code more readable and idiomatic. Developers with a background in OCaml or other functional programming language may be accustomed to different idioms. The following list summarizes the recommended F# operators. +The following operators are defined in the F# standard library and should be used instead of defining equivalents. Using these operators is recommended as it tends to make code more readable and idiomatic. The following list summarizes the recommended F# operators. ```fsharp x |> f // Forward pipeline @@ -507,266 +438,113 @@ x &&& y // Bitwise and, also for working with “flags” enumeration x ^^^ y // Bitwise xor, also for working with “flags” enumeration ``` -### Use prefix syntax for generics (`Foo`) in preference to postfix syntax (`T Foo`) - -F# inherits both the postfix ML style of naming generic types (for example, `int list`) as well as the prefix .NET style (for example, `list`). Prefer the .NET style, except for five specific types: - -1. For F# Lists, use the postfix form: `int list` rather than `list`. -2. For F# Options, use the postfix form: `int option` rather than `option`. -3. For F# Value Options, use the postfix form: `int voption` rather than `voption`. -4. For F# arrays, use the syntactic name `int[]` rather than `int array` or `array`. -5. For Reference Cells, use `int ref` rather than `ref` or `Ref`. - -For all other types, use the prefix form. - -## Formatting tuples - -A tuple instantiation should be parenthesized, and the delimiting commas within it should be followed by a single space, for example: `(1, 2)`, `(x, y, z)`. - -It is commonly accepted to omit parentheses in pattern matching of tuples: - -```fsharp -let (x, y) = z // Destructuring -let x, y = z // OK - -// OK -match x, y with -| 1, _ -> 0 -| x, 1 -> 0 -| x, y -> 1 -``` - -It is also commonly accepted to omit parentheses if the tuple is the return value of a function: - -```fsharp -// OK -let update model msg = - match msg with - | 1 -> model + 1, [] - | _ -> model, [ msg ] -``` - -In summary, prefer parenthesized tuple instantiations, but when using tuples for pattern matching or a return value, it is considered fine to avoid parentheses. - -## Formatting discriminated union declarations - -Indent `|` in type definition by four spaces: - -```fsharp -// OK -type Volume = - | Liter of float - | FluidOunce of float - | ImperialPint of float - -// Not OK -type Volume = -| Liter of float -| USPint of float -| ImperialPint of float -``` - -When there is a single short union, you can omit the leading `|`. - -```fsharp -type Address = Address of string -``` - -For a longer or multiline union, keep the `|`. - -```fsharp -[] -type SynBinding = - | SynBinding of - accessibility: SynAccess option * - kind: SynBindingKind * - mustInline: bool * - isMutable: bool * - attributes: SynAttributes * - xmlDoc: PreXmlDoc * - valData: SynValData * - headPat: SynPat * - returnInfo: SynBindingReturnInfo option * - expr: SynExpr * - range: range * - seqPoint: DebugPointAtBinding -``` - -You can also use triple-slash `///` comments. - -```fsharp -type Foobar = - /// Code comment - | Foobar of int -``` - -## Formatting discriminated unions - -Use a space before parenthesized/tupled parameters to discriminated union cases: - -```fsharp -// OK -let opt = Some ("A", 1) - -// Not OK -let opt = Some("A", 1) -``` - -Instantiated Discriminated Unions that split across multiple lines should give contained data a new scope with indentation: - -```fsharp -let tree1 = - BinaryNode - (BinaryNode (BinaryValue 1, BinaryValue 2), - BinaryNode (BinaryValue 3, BinaryValue 4)) -``` - -The closing parenthesis can also be on a new line: - -```fsharp -let tree1 = - BinaryNode( - BinaryNode (BinaryValue 1, BinaryValue 2), - BinaryNode (BinaryValue 3, BinaryValue 4) - ) -``` +## Formatting if expressions -## Formatting record declarations +Indentation of conditionals depends on the size and complexity of the expressions that make them up. +Write them on one line when: -Indent `{` in type definition by four spaces and start the field list on the same line: +- `cond`, `e1`, and `e2` are short +- `e1` and `e2` are not `if/then/else` expressions themselves. ```fsharp -// OK -type PostalAddress = - { Address: string - City: string - Zip: string } - member x.ZipAndCity = $"{x.Zip} {x.City}" +if cond then e1 else e2 +``` -// Not OK -type PostalAddress = - { Address: string - City: string - Zip: string } - member x.ZipAndCity = $"{x.Zip} {x.City}" +If any of the expressions are multi-line or `if/then/else` expressions. -// Unusual in F# -type PostalAddress = - { - Address: string - City: string - Zip: string - } +```fsharp +if cond then + e1 +else + e2 ``` -Placing the opening token on a new line and the closing token on a new line is preferable if you are declaring interface implementations or members on the record: +Multiple conditionals with `elif` and `else` are indented at the same scope as the `if` when they follow the rules of the one line `if/then/else` expressions. ```fsharp -// Declaring additional members on PostalAddress -type PostalAddress = - { - Address: string - City: string - Zip: string - } - member x.ZipAndCity = $"{x.Zip} {x.City}" - -type MyRecord = - { - SomeField: int - } - interface IMyInterface +if cond1 then e1 +elif cond2 then e2 +elif cond3 then e3 +else e4 ``` -## Formatting records - -Short records can be written in one line: +If any of the conditions or expressions is multi-line, the entire `if/then/else` expression is multi-line: ```fsharp -let point = { X = 1.0; Y = 0.0 } +if cond1 then + e1 +elif cond2 then + e2 +elif cond3 then + e3 +else + e4 ``` -Records that are longer should use new lines for labels: +If a condition is long, place it on the next line with an extra indent. +Align the `if` and the `then` keywords. ```fsharp -let rainbow = - { Boss = "Jeffrey" - Lackeys = ["Zippy"; "George"; "Bungle"] } +if + complexExpression a b && env.IsDevelopment() + || secondLongerExpression + aVeryLongparameterNameOne + aVeryLongparameterNameTwo + aVeryLongparameterNameThree +then + e1 + else + e2 ``` -Placing the opening token on a new line, the contents tabbed over one scope, and the closing token on a new line is preferable if you are: - -* Moving records around in code with different indentation scopes -* Piping them into a function +It is, however, better to refactor long conditions to a let binding or separate function: ```fsharp -let rainbow = - { - Boss1 = "Jeffrey" - Boss2 = "Jeffrey" - Boss3 = "Jeffrey" - Boss4 = "Jeffrey" - Boss5 = "Jeffrey" - Boss6 = "Jeffrey" - Boss7 = "Jeffrey" - Boss8 = "Jeffrey" - Lackeys = ["Zippy"; "George"; "Bungle"] - } - -type MyRecord = - { - SomeField: int - } - interface IMyInterface +let performAction = + complexExpression a b && env.IsDevelopment() + || secondLongerExpression + aVeryLongparameterNameOne + aVeryLongparameterNameTwo + aVeryLongparameterNameThree -let foo a = - a - |> Option.map (fun x -> - { - MyField = x - }) +if performAction then + e1 +else + e2 ``` -The same rules apply for list and array elements. - -## Formatting copy-and-update record expressions - -A copy-and-update record expression is still a record, so similar guidelines apply. +## Formatting union case expressions -Short expressions can fit on one line: +Use a space before parenthesized/tupled parameters to discriminated union cases: ```fsharp -let point2 = { point with X = 1; Y = 2 } +// OK +let opt = Some ("A", 1) + +// Not OK +let opt = Some("A", 1) ``` -Longer expressions should use new lines: +Discriminated unions that split across multiple lines should give contained data a new scope with indentation: ```fsharp -let rainbow2 = - { rainbow with - Boss = "Jeffrey" - Lackeys = [ "Zippy"; "George"; "Bungle" ] } +let tree1 = + BinaryNode + (BinaryNode (BinaryValue 1, BinaryValue 2), + BinaryNode (BinaryValue 3, BinaryValue 4)) ``` -And as with the record guidance, you may want to dedicate separate lines for the braces and indent one scope to the right with the expression. In some special cases, such as wrapping a value with an optional without parentheses, you may need to keep a brace on one line: +The closing parenthesis can also be on a new line: ```fsharp -type S = { F1: int; F2: string } -type State = { Foo: S option } - -let state = { Foo = Some { F1 = 1; F2 = "Hello" } } -let newState = - { - state with - Foo = - Some { - F1 = 0 - F2 = "" - } - } +let tree1 = + BinaryNode( + BinaryNode (BinaryValue 1, BinaryValue 2), + BinaryNode (BinaryValue 3, BinaryValue 4) + ) ``` -## Formatting lists and arrays +## Formatting list and array expressions Write `x :: l` with spaces around the `::` operator (`::` is an infix operator, hence surrounded by spaces). @@ -858,90 +636,95 @@ let daysOfWeek' includeWeekend = In some cases, `do...yield` may aid in readability. These cases, though subjective, should be taken into consideration. -## Formatting if expressions - -Indentation of conditionals depends on the size and complexity of the expressions that make them up. -Write them on one line when: +## Formatting record expressions -- `cond`, `e1`, and `e2` are short -- `e1` and `e2` are not `if/then/else` expressions themselves. +Short records can be written in one line: ```fsharp -if cond then e1 else e2 +let point = { X = 1.0; Y = 0.0 } ``` -If any of the expressions are multi-line or `if/then/else` expressions. +Records that are longer should use new lines for labels: ```fsharp -if cond then - e1 -else - e2 +let rainbow = + { Boss = "Jeffrey" + Lackeys = ["Zippy"; "George"; "Bungle"] } ``` -Multiple conditionals with `elif` and `else` are indented at the same scope as the `if` when they follow the rules of the one line `if/then/else` expressions. +Placing the opening token on a new line, the contents tabbed over one scope, and the closing token on a new line is preferable if you are: + +* Moving records around in code with different indentation scopes +* Piping them into a function ```fsharp -if cond1 then e1 -elif cond2 then e2 -elif cond3 then e3 -else e4 +let rainbow = + { + Boss1 = "Jeffrey" + Boss2 = "Jeffrey" + Boss3 = "Jeffrey" + Boss4 = "Jeffrey" + Boss5 = "Jeffrey" + Boss6 = "Jeffrey" + Boss7 = "Jeffrey" + Boss8 = "Jeffrey" + Lackeys = ["Zippy"; "George"; "Bungle"] + } + +type MyRecord = + { + SomeField: int + } + interface IMyInterface + +let foo a = + a + |> Option.map (fun x -> + { + MyField = x + }) ``` -If any of the conditions or expressions is multi-line, the entire `if/then/else` expression is multi-line: +The same rules apply for list and array elements. + +## Formatting copy-and-update record expressions + +A copy-and-update record expression is still a record, so similar guidelines apply. + +Short expressions can fit on one line: ```fsharp -if cond1 then - e1 -elif cond2 then - e2 -elif cond3 then - e3 -else - e4 +let point2 = { point with X = 1; Y = 2 } ``` -If a condition is long, place it on the next line with an extra indent. -Align the `if` and the `then` keywords. +Longer expressions should use new lines: ```fsharp -if - complexExpression a b && env.IsDevelopment() - || secondLongerExpression - aVeryLongparameterNameOne - aVeryLongparameterNameTwo - aVeryLongparameterNameThree - """ -Multiline - string - """ -then - e1 - else - e2 +let rainbow2 = + { rainbow with + Boss = "Jeffrey" + Lackeys = [ "Zippy"; "George"; "Bungle" ] } ``` -If you have a condition that is this long, first consider refactoring it into a separate function and calling that function instead +And as with the record guidance, you may want to dedicate separate lines for the braces and indent one scope to the right with the expression. In some special cases, such as wrapping a value with an optional without parentheses, you may need to keep a brace on one line: ```fsharp -let condition () = - complexExpression a b && env.IsDevelopment() - || secondLongerExpression - aVeryLongparameterNameOne - aVeryLongparameterNameTwo - aVeryLongparameterNameThree - """ -Multiline - string - """ +type S = { F1: int; F2: string } +type State = { Foo: S option } -if condition () then - e1 -else - e2 +let state = { Foo = Some { F1 = 1; F2 = "Hello" } } +let newState = + { + state with + Foo = + Some { + F1 = 0 + F2 = "" + } + } ``` -### Pattern matching constructs +### Formatting pattern matching Use a `|` for each clause of a match with no indentation. If the expression is short, you can consider using a single line if each subexpression is also simple. @@ -962,18 +745,33 @@ match l with If the expression on the right of the pattern matching arrow is too large, move it to the following line, indented one step from the `match`/`|`. ```fsharp +// OK match lam with | Var v -> 1 | Abs(x, body) -> 1 + sizeLambda body | App(lam1, lam2) -> sizeLambda lam1 + sizeLambda lam2 +``` +Aligning the arrows of a pattern match should be avoided. + +```fsharp +// OK +match lam with +| Var v -> v.Length +| Abstraction _ -> 2 + +// Not OK +match lam with +| Var v -> v.Length +| Abstraction _ -> 2 ``` -Pattern matching of anonymous functions, starting by `function`, should generally not indent too far. For example, indenting one scope as follows is fine: +Pattern matching via anonymous functions, useing the keyword `function`, should indent one level from the start of the previous line: ```fsharp +// OK lambdaList |> List.map (function | Abs(x, body) -> 1 + sizeLambda 0 body @@ -981,7 +779,8 @@ lambdaList | Var v -> 1) ``` -Pattern matching in functions defined by `let` or `let rec` should be indented four spaces after starting of `let`, even if `function` keyword is used: +The use of `function` in functions defined by `let` or `let rec` should in general be avoided in +favour of a `match`. If used, the pattern rules should align with the keyword `function`: ```fsharp let rec sizeLambda acc = @@ -991,8 +790,6 @@ let rec sizeLambda acc = | Var v -> succ acc ``` -We do not recommend aligning arrows. - ## Formatting try/with expressions Pattern matching on the exception type should be indented at the same level as `with`. @@ -1040,210 +837,400 @@ with printfn "Something went wrong: %A" ex ``` -## Formatting function parameter application +### Formatting named arguments -In general, most arguments are provided on the same line: +Named arguments should not have space surrounding the `=`: ```fsharp -let x = sprintf "\t%s - %i\n\r" x.IngredientName x.Quantity +// OK +let makeStreamReader x = new System.IO.StreamReader(path=x) -let printListWithOffset a list1 = - List.iter (fun elem -> printfn $"%d{a + elem}") list1 +// Not OK +let makeStreamReader x = new System.IO.StreamReader(path = x) ``` -When pipelines are concerned, the same is typically also true, where a curried function is applied as an argument on the same line: +### Formatting mutation expressions +Mutation expressions `location <- expr` are normally formatted on one line. +If multi-line formatting is required, place the right-hand-side expression on a new line. + +```fsharp +// OK +ctx.Response.Headers.[HeaderNames.ContentType] <- + Constants.jsonApiMediaType |> StringValues +ctx.Response.Headers.[HeaderNames.ContentLength] <- + bytes.Length |> string |> StringValues + +// Not OK +ctx.Response.Headers.[HeaderNames.ContentType] <- Constants.jsonApiMediaType + |> StringValues +ctx.Response.Headers.[HeaderNames.ContentLength] <- bytes.Length + |> string + |> StringValues ``` -let printListWithOffsetPiped a list1 = - list1 - |> List.iter (fun elem -> printfn $"%d{a + elem}") -``` -However, you may wish to pass arguments to a function on a new line, as a matter of readability or because the list of arguments or the argument names are too long. In that case, indent with one scope: +### Formatting object expressions + +Object expression members should be aligned with `member` being indented by one level. ```fsharp +let comparer = + { new IComparer with + member x.Compare(s1, s2) = + let rev (s: String) = + new String (Array.rev (s.ToCharArray())) + let reversed = rev s1 + reversed.CompareTo (rev s2) } +``` +## Formatting declarations + +Top-level declarations should be documented and formatted consistently. + +### Add blank lines between declarations + +Separate top-level function and class definitions with a single blank line. For example: + +```fsharp // OK -sprintf "\t%s - %i\n\r" - x.IngredientName x.Quantity +let thing1 = 1+1 + +let thing2 = 1+2 + +let thing3 = 1+3 + +type ThisThat = This | That + +// NOT OK +let thing1 = 1+1 +let thing2 = 1+2 +let thing3 = 1+3 +type ThisThat = This | That +``` + +If a construct has XML doc comments, add a blank line before the comment. + +```fsharp +/// This is a function +let thisFunction() = + 1 + 1 + +/// This is another function, note the blank line before this line +let thisFunction() = + 1 + 1 +``` + +### Formatting let and member declarations +When formatting `let` and `member` declarations, the right-hand side of a binding either goes on one line, or (if it's too long) goes on a new line indented one level. + +For example, the following are compliant: + +```fsharp // OK -sprintf - "\t%s - %i\n\r" - x.IngredientName x.Quantity +let a = + """ +foobar, long string +""" + +type File = + member this.SaveAsync(path: string) : Async = + async { + // IO operation + return () + } + +let c = + { Name = "Bilbo" + Age = 111 + Region = "The Shire" } + +let d = + while f do + printfn "%A" x +``` + +The following are non-compliant: + +```fsharp +// Not OK +let a = """ +foobar, long string +""" + +type File = + member this.SaveAsync(path: string) : Async = async { + // IO operation + return () + } + +let c = { + Name = "Bilbo" + Age = 111 + Region = "The Shire" +} + +let d = while f do + printfn "%A" x +``` + +Separate members with a single blank line and document and add a documentation comment: +```fsharp +/// This is a thing +type ThisThing(value: int) = + + /// Gets the value + member _.Value = value + + /// Returns twice the value + member _.TwiceValue() = value*2 +``` + +Extra blank lines may be used (sparingly) to separate groups of related functions. Blank lines may be omitted between a bunch of related one-liners (for example, a set of dummy implementations). Use blank lines in functions, sparingly, to indicate logical sections. + +### Formatting function and member arguments + +When defining a function, use white space around each argument. + +```fsharp // OK -let printVolumes x = - printf "Volume in liters = %f, in us pints = %f, in imperial = %f" - (convertVolumeToLiter x) - (convertVolumeUSPint x) - (convertVolumeImperialPint x) +let myFun (a: decimal) b c = a + b + c + +// Bad +let myFunBad (a:decimal)(b)c = a + b + c ``` -For lambda expressions, you may also want to consider placing the body of a lambda expression on a new line, indented by one scope, if it is long enough or followed by other arguments: +If you have a long function definition, place the parameters on new lines and indent them to match the indentation level of the subsequent parameter. ```fsharp -let printListWithOffset a list1 = - List.iter - (fun elem -> - printfn $"A very long line to format the value: %d{a + elem}") - list1 +module M = + let longFunctionWithLotsOfParameters + (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) + (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse) + (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse) + = + // ... the body of the method follows + + let longFunctionWithLotsOfParametersAndReturnType + (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) + (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse) + (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse) + : ReturnType = + // ... the body of the method follows + + let longFunctionWithLongTupleParameter + ( + aVeryLongParam: AVeryLongTypeThatYouNeedToUse, + aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, + aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse + ) = + // ... the body of the method follows + + let longFunctionWithLongTupleParameterAndReturnType + ( + aVeryLongParam: AVeryLongTypeThatYouNeedToUse, + aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, + aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse + ) : ReturnType = + // ... the body of the method follows ``` -If the lambda argument is the last argument in a function application, place all arguments until the arrow on the same line. +This also applies to members, constructors, and parameters using tuples: ```fsharp -Target.create "Build" (fun ctx -> - // code - // here - ()) +type TypeWithLongMethod() = + member _.LongMethodWithLotsOfParameters + ( + aVeryLongParam: AVeryLongTypeThatYouNeedToUse, + aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, + aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse + ) = + // ... the body of the method -let printListWithOffsetPiped a list1 = - list1 - |> List.iter (fun elem -> - printfn $"A very long line to format the value: %d{a + elem}") +type TypeWithLongConstructor + ( + aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse, + aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse, + aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse + ) = + // ... the body of the class follows ``` -Treat match lambda's in a similar fashion. +If the parameters are curried, place the `=` character along with any return type on a new line: ```fsharp -functionName arg1 arg2 arg3 (function - | Choice1of2 x - | Choice2of2 y) +type TypeWithLongCurriedMethods() = + member _.LongMethodWithLotsOfCurriedParamsAndReturnType + (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) + (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse) + (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse) + : ReturnType = + // ... the body of the method + + member _.LongMethodWithLotsOfCurriedParams + (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) + (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse) + (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse) + = + // ... the body of the method ``` -Unless there are many leading or multiline arguments before the lambda. In that case, indent all arguments with one scope. +This is a way to avoid too long lines (in case return type might have long name) and have less line-damage when adding parameters. + +### Formatting operator declarations + +Always use white space to surround an operator definition: ```fsharp -functionName - arg1 - arg2 - arg3 - (fun arg4 -> - bodyExpr) +// OK +let ( !> ) x f = f x -functionName - arg1 - arg2 - arg3 - (function - | Choice1of2 x - | Choice2of2 y) +// Bad +let (!>) x f = f x ``` -If the body of a lambda expression is multiple lines long, you should consider refactoring it into a locally-scoped function. +For any custom operator that starts with `*` and that has more than one character, you need to add a white space to the beginning of the definition to avoid a compiler ambiguity. Because of this, we recommend that you simply surround the definitions of all operators with a single white-space character. + +## Formatting record declarations -Note that in combination of infix operators the body of a lambda expression should be indented one indent further from the current line. -This is not the case when all arguments are indented one from the function application, there the indent should be in respect to the function name. +For record declarations, indent `{` in type definition by four spaces, start the field list on the same line and align any members with the `{` token: ```fsharp -// With 4 spaces indentation -list1 -|> List.iter (fun elem -> - // one indent starting from the pipe - printfn $"A very long line to format the value: %d{elem}") +// OK +type PostalAddress = + { Address: string + City: string + Zip: string } + member x.ZipAndCity = $"{x.Zip} {x.City}" + +// Not OK +type PostalAddress = { + Address: string + City: string + Zip: string + } + member x.ZipAndCity = $"{x.Zip} {x.City}" +``` + +When XML documentation is added for record fields, it becomes normal to indent and add whitespace: +```fsharp +type PostalAddress = + { + /// The address + Address: string + + /// The city + City: string + + /// The zip code + Zip: string + } + + /// Format the zip code and the city + member x.ZipAndCity = $"{x.Zip} {x.City}" +``` + +Placing the opening token on a new line and the closing token on a new line is preferable if you are declaring interface implementations or members on the record: + +```fsharp +// Declaring additional members on PostalAddress +type PostalAddress = + { + /// The address + Address: string -list1 -|> List.fold - // one indent from the function name - someLongParam - anotherLongParam + /// The city + City: string -// With 2 spaces indentation -list1 -|> List.fold - // one indent from the function name - someLongParam - anotherLongParam + /// The zip code + Zip: string + } + member x.ZipAndCity = $"{x.Zip} {x.City}" -list1 -|> List.iter (fun elem -> - // one indent starting from the pipe - printfn $"A very long line to format the value: %d{elem}") +type MyRecord = + { + /// The record field + SomeField: int + } + interface IMyInterface ``` -When the function take a single multiline tuple argument, the same rules for [Formatting constructors, static members, and member invocations](#formatting-constructors-static-members-and-member-invocations) apply. +## Formatting discriminated union declarations + +For discriminated union declarations, indent `|` in type definition by four spaces: ```fsharp -let myFunction (a: int, b: string, c: int, d: bool) = - () +// OK +type Volume = + | Liter of float + | FluidOunce of float + | ImperialPint of float -myFunction( - 478815516, - "A very long string making all of this multi-line", - 1515, - false -) +// Not OK +type Volume = +| Liter of float +| USPint of float +| ImperialPint of float ``` -### Formatting infix operators +When there is a single short union, you can omit the leading `|`. -Separate operators by spaces. Obvious exceptions to this rule are the `!` and `.` operators. +```fsharp +type Address = Address of string +``` -Infix expressions are OK to lineup on same column: +For a longer or multi-line union, keep the `|` and place each union field on a new line, with the separating `*` at the end of each line. ```fsharp -acc + -(sprintf "\t%s - %i\n\r" - x.IngredientName x.Quantity) - -let function1 arg1 arg2 arg3 arg4 = - arg1 + arg2 + - arg3 + arg4 +[] +type SynBinding = + | SynBinding of + accessibility: SynAccess option * + kind: SynBindingKind * + mustInline: bool * + isMutable: bool * + attributes: SynAttributes * + xmlDoc: PreXmlDoc * + valData: SynValData * + headPat: SynPat * + returnInfo: SynBindingReturnInfo option * + expr: SynExpr * + range: range * + seqPoint: DebugPointAtBinding ``` -### Formatting pipeline operators or mutable assignments - -Pipeline `|>` operators should go underneath the expressions they operate on. +When documentation comments are added, use an empty line before each `///` comment. ```fsharp -// 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 +/// The volume +type Volume = -// 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 + /// The volume in liters + | Liter of float -// Not OK either -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 + /// The volume in fluid ounces + | FluidOunce of float + + /// The volume in imperial pints + | ImperialPint of float ``` -This also applies to mutable setters: +### Formatting literal declarations + +[F# literals](../language-reference/literals.md) using the `Literal` attribute should place the attribute on its own line and use PascalCase naming: ```fsharp -// Preferred approach -ctx.Response.Headers.[HeaderNames.ContentType] <- - Constants.jsonApiMediaType |> StringValues -ctx.Response.Headers.[HeaderNames.ContentLength] <- - bytes.Length |> string |> StringValues +[] +let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__ -// Not OK -ctx.Response.Headers.[HeaderNames.ContentType] <- Constants.jsonApiMediaType - |> StringValues -ctx.Response.Headers.[HeaderNames.ContentLength] <- bytes.Length - |> string - |> StringValues +[] +let MyUrl = "www.mywebsitethatiamworkingwith.com" ``` -### Formatting modules +Avoid placing the attribute on the same line as the value. + +## Formatting module declarations Code in a local module must be indented relative to the module, but code in a top-level module should not be indented. Namespace elements do not have to be indented. @@ -1263,100 +1250,201 @@ module A2 = let function2 a b = a * a - b * b ``` -### Formatting object expressions and interfaces +### Formatting do declarations -Object expressions and interfaces should be aligned in the same way with `member` being indented after four spaces. +In type declarations, module declarations and computation expressions, the use of `do` or `do!` is sometimes required for side-effecting operations. +When these span multiple lines, use indentation and a new line to keep the indentation consistent with `let`/`let!`. Here is an example using `do` in a class: ```fsharp -let comparer = - { new IComparer with - member x.Compare(s1, s2) = - let rev (s: String) = - new String (Array.rev (s.ToCharArray())) - let reversed = rev s1 - reversed.CompareTo (rev s2) } +// OK +type Foo () = + let foo = + fooBarBaz + |> loremIpsumDolorSitAmet + |> theQuickBrownFoxJumpedOverTheLazyDog + do + fooBarBaz + |> loremIpsumDolorSitAmet + |> theQuickBrownFoxJumpedOverTheLazyDog + +// Bad - notice the "do" expression is indented one space less than the `let` expression +type Foo () = + let foo = + fooBarBaz + |> loremIpsumDolorSitAmet + |> theQuickBrownFoxJumpedOverTheLazyDog + do fooBarBaz + |> loremIpsumDolorSitAmet + |> theQuickBrownFoxJumpedOverTheLazyDog +``` + +Here is an example with `do!` using 2 spaces of indentation (because with `do!` there is coincidentally no difference between the approaches when using 4 spaces of indentation): + +```fsharp +// OK +async { + let! foo = + fooBarBaz + |> loremIpsumDolorSitAmet + |> theQuickBrownFoxJumpedOverTheLazyDog + do! + fooBarBaz + |> loremIpsumDolorSitAmet + |> theQuickBrownFoxJumpedOverTheLazyDog +} + +// Bad - notice the "do!" expression is indented two spaces more than the `let!` expression +async { + let! foo = + fooBarBaz + |> loremIpsumDolorSitAmet + |> theQuickBrownFoxJumpedOverTheLazyDog + do! fooBarBaz + |> loremIpsumDolorSitAmet + |> theQuickBrownFoxJumpedOverTheLazyDog +} ``` -### Formatting white space in expressions +## Formatting types and type annotations + +### For types, prefer prefix syntax for generics (`Foo`), with some specific exceptions + +F# allows both postfix style of writing generic types (for example, `int list`) as well as the prefix style (for example, `list`). +Postfix style can only be used with a single type argument. +Always prefer the .NET style, except for five specific types: + +1. For F# Lists, use the postfix form: `int list` rather than `list`. +2. For F# Options, use the postfix form: `int option` rather than `option`. +3. For F# Value Options, use the postfix form: `int voption` rather than `voption`. +4. For F# arrays, use the syntactic name `int[]` rather than `int array` or `array`. +5. For Reference Cells, use `int ref` rather than `ref` or `Ref`. + +For all other types, use the prefix form. + +### Formatting function types -Avoid extraneous white space in F# expressions. +When defining the signature of a function, use white space around the `->` symbol: ```fsharp // OK -spam (ham.[1]) +type MyFun = int -> int -> string -// Not OK -spam ( ham.[ 1 ] ) +// Bad +type MyFunBad = int->int->string ``` -Named arguments should also not have space surrounding the `=`: +### Formatting value and argument type annotations + +When defining values or arguments with type annotations, use white space after the `:` symbol, but not before: ```fsharp // OK -let makeStreamReader x = new System.IO.StreamReader(path=x) +let complexFunction (a: int) (b: int) c = a + b + c -// Not OK -let makeStreamReader x = new System.IO.StreamReader(path = x) +let simpleValue: int = 0 // Type annotation for let-bound value + +type C() = + member _.Property: int = 1 + +// Bad +let complexFunctionPoorlyAnnotated (a :int) (b :int) (c:int) = a + b + c +let simpleValuePoorlyAnnotated1:int = 1 +let simpleValuePoorlyAnnotated2 :int = 2 ``` -### Formatting constructors, static members, and member invocations +### Formatting return type annotations -If the expression is short, separate arguments with spaces and keep it in one line. +In function or member return type annotations, use white space before and after the `:` symbol: ```fsharp -let person = new Person(a1, a2) +// OK +let myFun (a: decimal) b c : decimal = a + b + c // Type annotation for the return type of a function -let myRegexMatch = Regex.Match(input, regex) +let anotherFun (arg: int) : unit = () // Type annotation for return type of a function -let untypedRes = checker.ParseFile(file, source, opts) +type C() = + member _.SomeMethod(x: int) : int = 1 // Type annotation for return type of a member + +// Bad +let myFunBad (a: decimal) b c:decimal = a + b + c + +let anotherFunBad (arg: int): unit = () + +type C() = + member _.SomeMethodBad(x: int): int = 1 ``` -If the expression is long, use newlines and indent one scope, rather than indenting to the bracket. +## Formatting types in signatures + +When writing full function types in signatures, it is sometimes necessary to split the arguments +over multiple lines. For a tupled function the arguments are separated by `*`, +placed at the end of each line. The return type is indented. For example, consider a function with the +following implementation: ```fsharp -let person = - new Person( - argument1, - argument2 - ) +let SampleTupledFunction(arg1, arg2, arg3, arg4) = ... +``` -let myRegexMatch = - Regex.Match( - "my longer input string with some interesting content in it", - "myRegexPattern" - ) +In the corresponding signature file (`.fsi` extension) the function can be formatted as follows when +multi-line formatting is required: -let untypedRes = - checker.ParseFile( - fileName, - sourceText, - parsingOptionsWithDefines - ) +```fsharp +val SampleTupledFunction: + arg1: string * + arg2: string * + arg3: int * + arg4: int + -> int list ``` -The same rules apply even if there is only a single multiline argument. +Likewise consider a curried function: ```fsharp -let poemBuilder = StringBuilder() -poemBuilder.AppendLine( - """ -The last train is nearly due -The Underground is closing soon -And in the dark, deserted station -Restless in anticipation -A man waits in the shadows - """ -) +let SampleCurriedFunction arg1 arg2 arg3 arg4 = ... +``` -Option.traverse( - create - >> Result.setError [ invalidHeader "Content-Checksum" ] -) +In the corresponding signature file the `->` are placed at the start of each line: + +```fsharp +val SampleCurriedFunction: + arg1: string + -> arg2: string + -> arg3: int + -> arg4: int + -> int list +``` + +Likewise consider a function that takes a mix of curried and tupled arguments: + +```fsharp +// Typical call syntax: +let SampleMixedFunction + (arg1, arg2) + (arg3, arg4, arg5) + (arg6, arg7) + (arg8, arg9, arg10) = .. +``` + +In the corresponding signature file the `->` are placed at the end of each argument except the last: + +```fsharp +val SampleMixedFunction: + arg1: string * + arg2: string + -> arg3: string * + arg4: string * + arg5: TType + -> arg6: TType * + arg7: TType * + -> arg8: TType * + arg9: TType * + arg10: TType + -> TType list ``` -## Formatting generic type arguments and constraints +### Formatting explicit generic type arguments and constraints -The guidelines below apply to both functions, members, and type definitions. +The guidelines below apply to function definitions, member definitions, and type definitions. Keep generic type arguments and constraints on a single line if it’s not too long: @@ -1449,20 +1537,6 @@ type MyRecord = When applied to a parameter, they must be on the same line and separated by a `;` separator. -## Formatting literals - -[F# literals](../language-reference/literals.md) using the `Literal` attribute should place the attribute on its own line and use PascalCase naming: - -```fsharp -[] -let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__ - -[] -let MyUrl = "www.mywebsitethatiamworkingwith.com" -``` - -Avoid placing the attribute on the same line as the value. - ## Formatting computation expression operations When creating custom operations for [computation expressions](../language-reference/computation-expressions.md), it is recommended to use camelCase naming: @@ -1495,11 +1569,8 @@ let myNumber = addOne addOne addOne - subtractOne - divideBy 2 - multiplyBy 10 } ``` From bbbe6faa54476028f86e3f62401e5387d9c5860e Mon Sep 17 00:00:00 2001 From: Don Syme Date: Mon, 6 Sep 2021 14:06:18 +0100 Subject: [PATCH 2/9] Update formatting.md --- docs/fsharp/style-guide/formatting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 6a6fba3063e3d..8d7dab7fc73d3 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -13,7 +13,7 @@ This article offers guidelines for how to format your code so that your F# code These guidelines are based on [A comprehensive guide to F# Formatting Conventions](https://github.com/dungpa/fantomas/blob/master/docs/FormattingConventions.md) by [Anh-Dung Phan](https://github.com/dungpa). -See also [Coding conventions](conventions/) and [Component design guidelines](component-design-guidelines), which also covers naming conventions. +See also [Coding conventions](../conventions) and [Component design guidelines](../component-design-guidelines), which also covers naming conventions. ## General rules for formatting From e6556ec10a88c70a3638652e286014b6a3705e5a Mon Sep 17 00:00:00 2001 From: Don Syme Date: Mon, 6 Sep 2021 14:42:16 +0100 Subject: [PATCH 3/9] Update formatting.md --- docs/fsharp/style-guide/formatting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 8d7dab7fc73d3..a2fed2a445ebb 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -13,7 +13,7 @@ This article offers guidelines for how to format your code so that your F# code These guidelines are based on [A comprehensive guide to F# Formatting Conventions](https://github.com/dungpa/fantomas/blob/master/docs/FormattingConventions.md) by [Anh-Dung Phan](https://github.com/dungpa). -See also [Coding conventions](../conventions) and [Component design guidelines](../component-design-guidelines), which also covers naming conventions. +See also [Coding conventions](../conventions.md) and [Component design guidelines](../component-design-guidelines.md), which also covers naming conventions. ## General rules for formatting From 69f2e9930aad8d767965d42527247b5d8afb3c2b Mon Sep 17 00:00:00 2001 From: Don Syme Date: Mon, 6 Sep 2021 14:44:08 +0100 Subject: [PATCH 4/9] Update formatting.md --- docs/fsharp/style-guide/formatting.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index a2fed2a445ebb..0718677112c58 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -1114,6 +1114,7 @@ type PostalAddress = { ``` When XML documentation is added for record fields, it becomes normal to indent and add whitespace: + ```fsharp type PostalAddress = { From 6c5b6acea07bb0bb1d3ab2433db836e89cfd1ede Mon Sep 17 00:00:00 2001 From: Don Syme Date: Mon, 6 Sep 2021 14:46:14 +0100 Subject: [PATCH 5/9] Update formatting.md --- docs/fsharp/style-guide/formatting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 0718677112c58..8b03322360d94 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -13,7 +13,7 @@ This article offers guidelines for how to format your code so that your F# code These guidelines are based on [A comprehensive guide to F# Formatting Conventions](https://github.com/dungpa/fantomas/blob/master/docs/FormattingConventions.md) by [Anh-Dung Phan](https://github.com/dungpa). -See also [Coding conventions](../conventions.md) and [Component design guidelines](../component-design-guidelines.md), which also covers naming conventions. +See also [Coding conventions](conventions.md) and [Component design guidelines](component-design-guidelines.md), which also covers naming conventions. ## General rules for formatting From 6415eee20ed0a745aad802a5bba7bfeaac890ad6 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Mon, 6 Sep 2021 18:56:56 +0100 Subject: [PATCH 6/9] Update formatting.md --- docs/fsharp/style-guide/formatting.md | 196 ++++++++++++++------------ 1 file changed, 105 insertions(+), 91 deletions(-) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 8b03322360d94..9c6a18b499a7b 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -39,8 +39,7 @@ The [Fantomas code formatter](https://github.com/fsprojects/fantomas/#fantomas) settings correspond to this style guide. We strongly recommend the use of this code formatter. Within F# teams, code formatting specifications should be agreed and codified in terms -of a agreed settings for the code formatter. If the code formatter doesn't implement the setting you require, contribute a new -setting to the project. +of an agreed settings file for the code formatter checked into the team repository. ### Avoid formatting that is sensitive to name length @@ -69,10 +68,10 @@ Avoid extraneous white space in F# code, except where described in this style gu ```fsharp // OK -spam (ham.[1]) +spam (ham 1) // Not OK -spam ( ham.[ 1 ] ) +spam ( ham 1 ) ``` ## Formatting comments @@ -121,8 +120,9 @@ A tuple instantiation should be parenthesized, and the delimiting commas within It is commonly accepted to omit parentheses in pattern matching of tuples: ```fsharp -let (x, y) = z // Destructuring -let x, y = z // OK +// OK +let (x, y) = z +let x, y = z // OK match x, y with @@ -162,6 +162,28 @@ someFunction1 x.IngredientName someFunction1(x.IngredientName) ``` +By convention, space is added when applying functions to tupled arguments: +```fsharp +// OK +someFunction1 (x.IngredientName, x.Quantity) + +// OK, but formatting tools will add the extra space by default +someFunction1(x.IngredientName, x.Quantity) +``` + +For capitalized methods accepting tupled arguments, no space is added. This is because these are often used with fluent programming: + +```fsharp +// OK - Methods accepting tuples are applied without a space: +String.Format(x.IngredientName, x.Quantity) + +// OK - it's important not to use a space if using fluent programming with chained '.' invocations +String.Format(x.IngredientName, x.Quantity).Length + +// OK, but formatting tools will remove the extra space by default +String.Format (x.IngredientName, x.Quantity) +``` + You may need to pass arguments to a function on a new line, as a matter of readability or because the list of arguments or the argument names are too long. In that case, indent one level: ```fsharp @@ -192,20 +214,19 @@ When the function takes a single multi-line tupled argument, place each argument ```fsharp // OK -someTupledFunction( +someTupledFunction ( 478815516, "A very long string making all of this multi-line", 1515, false ) -// OK +// OK, but formatting tools will reformat to the above someTupledFunction (478815516, "A very long string making all of this multi-line", 1515, false) -) ``` If argument expressions are short, separate arguments with spaces and keep it in one line. @@ -230,9 +251,10 @@ let person = // OK let myRegexMatch = - Regex.Match - ("my longer input string with some interesting content in it", - "myRegexPattern") + Regex.Match( + "my longer input string with some interesting content in it", + "myRegexPattern" + ) // OK let untypedRes = @@ -481,8 +503,7 @@ else e4 ``` -If a condition is long, place it on the next line with an extra indent. -Align the `if` and the `then` keywords. +If a condition is long, the `then` keyword is still placed at the end of the expression. ```fsharp if @@ -490,14 +511,13 @@ if || secondLongerExpression aVeryLongparameterNameOne aVeryLongparameterNameTwo - aVeryLongparameterNameThree -then + aVeryLongparameterNameThree then e1 else e2 ``` -It is, however, better to refactor long conditions to a let binding or separate function: +It is, however, better style to refactor long conditions to a let binding or separate function: ```fsharp let performAction = @@ -515,28 +535,21 @@ else ## Formatting union case expressions -Use a space before parenthesized/tupled parameters to discriminated union cases: +Applying discriminated union cases follows the same rules as function and method applications. +That is, because the name is capitalized, code formatters will remove a space before a tuple: ```fsharp // OK -let opt = Some ("A", 1) - -// Not OK let opt = Some("A", 1) -``` - -Discriminated unions that split across multiple lines should give contained data a new scope with indentation: -```fsharp -let tree1 = - BinaryNode - (BinaryNode (BinaryValue 1, BinaryValue 2), - BinaryNode (BinaryValue 3, BinaryValue 4)) +// OK, but code formatters will remove the space +let opt = Some ("A", 1) ``` -The closing parenthesis can also be on a new line: +Like function applications, constructions that split across multiple lines should use indentation: ```fsharp +// OK let tree1 = BinaryNode( BinaryNode (BinaryValue 1, BinaryValue 2), @@ -559,18 +572,18 @@ Always use at least one space between two distinct brace-like operators. For exa ```fsharp // OK -[ { IngredientName = "Green beans"; Quantity = 250 } - { IngredientName = "Pine nuts"; Quantity = 250 } - { IngredientName = "Feta cheese"; Quantity = 250 } - { IngredientName = "Olive oil"; Quantity = 10 } - { IngredientName = "Lemon"; Quantity = 1 } ] +[ { Ingredient = "Green beans"; Quantity = 250 } + { Ingredient = "Pine nuts"; Quantity = 250 } + { Ingredient = "Feta cheese"; Quantity = 250 } + { Ingredient = "Olive oil"; Quantity = 10 } + { Ingredient = "Lemon"; Quantity = 1 } ] // Not OK -[{ IngredientName = "Green beans"; Quantity = 250 } - { IngredientName = "Pine nuts"; Quantity = 250 } - { IngredientName = "Feta cheese"; Quantity = 250 } - { IngredientName = "Olive oil"; Quantity = 10 } - { IngredientName = "Lemon"; Quantity = 1 }] +[{ Ingredient = "Green beans"; Quantity = 250 } + { Ingredient = "Pine nuts"; Quantity = 250 } + { Ingredient = "Feta cheese"; Quantity = 250 } + { Ingredient = "Olive oil"; Quantity = 10 } + { Ingredient = "Lemon"; Quantity = 1 }] ``` The same guideline applies for lists or arrays of tuples. @@ -579,17 +592,15 @@ Lists and arrays that split across multiple lines follow a similar rule as recor ```fsharp let pascalsTriangle = - [| - [| 1 |] - [| 1; 1 |] - [| 1; 2; 1 |] - [| 1; 3; 3; 1 |] - [| 1; 4; 6; 4; 1 |] - [| 1; 5; 10; 10; 5; 1 |] - [| 1; 6; 15; 20; 15; 6; 1 |] - [| 1; 7; 21; 35; 35; 21; 7; 1 |] - [| 1; 8; 28; 56; 70; 56; 28; 8; 1 |] - |] + [| [| 1 |] + [| 1; 1 |] + [| 1; 2; 1 |] + [| 1; 3; 3; 1 |] + [| 1; 4; 6; 4; 1 |] + [| 1; 5; 10; 10; 5; 1 |] + [| 1; 6; 15; 20; 15; 6; 1 |] + [| 1; 7; 21; 35; 35; 21; 7; 1 |] + [| 1; 8; 28; 56; 70; 56; 28; 8; 1 |] |] ``` And as with records, declaring the opening and closing brackets on their own line will make moving code around and piping into functions easier. @@ -647,42 +658,30 @@ let point = { X = 1.0; Y = 0.0 } Records that are longer should use new lines for labels: ```fsharp +// OK let rainbow = { Boss = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] } ``` -Placing the opening token on a new line, the contents tabbed over one scope, and the closing token on a new line is preferable if you are: - -* Moving records around in code with different indentation scopes -* Piping them into a function +Placing the `{` and `}` on new lines with contents indented is possible, however code formatters may reformat this by default: ```fsharp +// OK, this is the default formatting for code formatters +let rainbow = + { Boss1 = "Jeffrey" + Boss2 = "Jeffrey" + Boss3 = "Jeffrey" + Lackeys = ["Zippy"; "George"; "Bungle"] } + +// OK, but code formatters will reformat to the above by default let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" - Boss4 = "Jeffrey" - Boss5 = "Jeffrey" - Boss6 = "Jeffrey" - Boss7 = "Jeffrey" - Boss8 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] } - -type MyRecord = - { - SomeField: int - } - interface IMyInterface - -let foo a = - a - |> Option.map (fun x -> - { - MyField = x - }) ``` The same rules apply for list and array elements. @@ -694,25 +693,30 @@ A copy-and-update record expression is still a record, so similar guidelines app Short expressions can fit on one line: ```fsharp +// OK let point2 = { point with X = 1; Y = 2 } ``` Longer expressions should use new lines: ```fsharp +// OK let rainbow2 = { rainbow with Boss = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] } ``` -And as with the record guidance, you may want to dedicate separate lines for the braces and indent one scope to the right with the expression. In some special cases, such as wrapping a value with an optional without parentheses, you may need to keep a brace on one line: +You may want to dedicate separate lines for the braces and indent one scope to the right with the expression, however +code formatters may . In some special cases, such as wrapping a value with an optional without parentheses, you may need to keep a brace on one line: ```fsharp -type S = { F1: int; F2: string } -type State = { Foo: S option } +// OK +let newState = + { state with + Foo = Some { F1 = 0; F2 = "" } } -let state = { Foo = Some { F1 = 1; F2 = "Hello" } } +// Not OK, code formatters will reformat to the above by default let newState = { state with @@ -735,7 +739,7 @@ match l with | _ :: tail -> findDavid tail | [] -> failwith "Couldn't find David" -// Not OK +// Not OK, code formatters will reformat to the above by default match l with | { him = x; her = "Posh" } :: tail -> x | _ :: tail -> findDavid tail @@ -762,13 +766,13 @@ match lam with | Var v -> v.Length | Abstraction _ -> 2 -// Not OK +// Not OK, code formatters will reformat to the above by default match lam with | Var v -> v.Length | Abstraction _ -> 2 ``` -Pattern matching via anonymous functions, useing the keyword `function`, should indent one level from the start of the previous line: +Pattern matching introduced by using the keyword `function` should indent one level from the start of the previous line: ```fsharp // OK @@ -783,6 +787,7 @@ The use of `function` in functions defined by `let` or `let rec` should in gener favour of a `match`. If used, the pattern rules should align with the keyword `function`: ```fsharp +// OK let rec sizeLambda acc = function | Abs(x, body) -> sizeLambda (succ acc) body @@ -858,10 +863,11 @@ If multi-line formatting is required, place the right-hand-side expression on a // OK ctx.Response.Headers.[HeaderNames.ContentType] <- Constants.jsonApiMediaType |> StringValues + ctx.Response.Headers.[HeaderNames.ContentLength] <- bytes.Length |> string |> StringValues -// Not OK +// Not OK, code formatters will reformat to the above by default ctx.Response.Headers.[HeaderNames.ContentType] <- Constants.jsonApiMediaType |> StringValues ctx.Response.Headers.[HeaderNames.ContentLength] <- bytes.Length @@ -877,8 +883,7 @@ Object expression members should be aligned with `member` being indented by one let comparer = { new IComparer with member x.Compare(s1, s2) = - let rev (s: String) = - new String (Array.rev (s.ToCharArray())) + let rev (s: String) = new String (Array.rev (s.ToCharArray())) let reversed = rev s1 reversed.CompareTo (rev s2) } ``` @@ -901,7 +906,7 @@ let thing3 = 1+3 type ThisThat = This | That -// NOT OK +// Not OK let thing1 = 1+1 let thing2 = 1+2 let thing3 = 1+3 @@ -953,7 +958,7 @@ let d = The following are non-compliant: ```fsharp -// Not OK +// Not OK, code formatters will reformat to the above by default let a = """ foobar, long string """ @@ -995,15 +1000,16 @@ When defining a function, use white space around each argument. ```fsharp // OK -let myFun (a: decimal) b c = a + b + c +let myFun (a: decimal) (b: int) c = a + b + c -// Bad -let myFunBad (a:decimal)(b)c = a + b + c +// Not OK, code formatters will reformat to the above by default +let myFunBad (a:decimal)(b:int)c = a + b + c ``` If you have a long function definition, place the parameters on new lines and indent them to match the indentation level of the subsequent parameter. ```fsharp +// OK module M = let longFunctionWithLotsOfParameters (aVeryLongParam: AVeryLongTypeThatYouNeedToUse) @@ -1080,13 +1086,13 @@ This is a way to avoid too long lines (in case return type might have long name) ### Formatting operator declarations -Always use white space to surround an operator definition: +Optionally use white space to surround an operator definition: ```fsharp // OK let ( !> ) x f = f x -// Bad +// OK let (!>) x f = f x ``` @@ -1103,14 +1109,20 @@ type PostalAddress = City: string Zip: string } member x.ZipAndCity = $"{x.Zip} {x.City}" +``` -// Not OK +Do not place the `{` at the end of the type declaration line, and do not use `with`/`end` for members, which are redundant. + +```fsharp +// Not OK, code formatters will reformat to the above by default type PostalAddress = { Address: string City: string Zip: string } + with member x.ZipAndCity = $"{x.Zip} {x.City}" + end ``` When XML documentation is added for record fields, it becomes normal to indent and add whitespace: @@ -1263,6 +1275,7 @@ type Foo () = fooBarBaz |> loremIpsumDolorSitAmet |> theQuickBrownFoxJumpedOverTheLazyDog + do fooBarBaz |> loremIpsumDolorSitAmet @@ -1288,6 +1301,7 @@ async { fooBarBaz |> loremIpsumDolorSitAmet |> theQuickBrownFoxJumpedOverTheLazyDog + do! fooBarBaz |> loremIpsumDolorSitAmet From 89a885aaa57ee3005af5a95051ce2a3b0e850beb Mon Sep 17 00:00:00 2001 From: Don Syme Date: Mon, 6 Sep 2021 19:02:54 +0100 Subject: [PATCH 7/9] Update formatting.md --- docs/fsharp/style-guide/formatting.md | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 9c6a18b499a7b..3581d6f128fbc 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -162,25 +162,35 @@ someFunction1 x.IngredientName someFunction1(x.IngredientName) ``` -By convention, space is added when applying functions to tupled arguments: +In default formatting conventions, a space is added when applying lower-case functions to tupled or parenthesized arguments: + ```fsharp // OK -someFunction1 (x.IngredientName, x.Quantity) +someFunction2 () + +// OK +someFunction3 (x.Quantity1 + x.Quantity2) -// OK, but formatting tools will add the extra space by default -someFunction1(x.IngredientName, x.Quantity) +// Not OK, formatting tools will add the extra space by default +someFunction2() + +// Not OK, formatting tools will add the extra space by default +someFunction3(x.IngredientName, x.Quantity) ``` -For capitalized methods accepting tupled arguments, no space is added. This is because these are often used with fluent programming: +In default formatting conventions, no space is added when applying capitalized methods to tupled arguments. This is because these are often used with fluent programming: ```fsharp -// OK - Methods accepting tuples are applied without a space: +// OK - Methods accepting parenthesize arguments are applied without a space +SomeClass.Invoke() + +// OK - Methods accepting tuples are applied without a space String.Format(x.IngredientName, x.Quantity) -// OK - it's important not to use a space if using fluent programming with chained '.' invocations -String.Format(x.IngredientName, x.Quantity).Length +// Not OK, formatting tools will remove the extra space by default +SomeClass.Invoke () -// OK, but formatting tools will remove the extra space by default +// Not OK, formatting tools will remove the extra space by default String.Format (x.IngredientName, x.Quantity) ``` From 6abc4fbfe1f03e843fab14019afe90d5d7972e54 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 14 Sep 2021 12:21:47 +0100 Subject: [PATCH 8/9] Update formatting.md --- docs/fsharp/style-guide/formatting.md | 134 ++++++++++++++------------ 1 file changed, 72 insertions(+), 62 deletions(-) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 3581d6f128fbc..319cb65df8ff1 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -102,6 +102,8 @@ For formatting XML doc comments, see "Formatting declarations" below. ## Formatting expressions +This section discusses formatting expressions of different kinds. + ### Formatting string expressions String literals and interpolated strings can just be left on a single line, regardless of how long the line is. @@ -113,10 +115,16 @@ let serviceStorageConnection = Multi-line interpolated expressions are discouraged. Instead, bind the expression result to a value and use that in the interpolated string. -## Formatting tuple expressions +### Formatting tuple expressions A tuple instantiation should be parenthesized, and the delimiting commas within it should be followed by a single space, for example: `(1, 2)`, `(x, y, z)`. +```fsharp +// OK +let pair = (1, 2) +let triples = [ (1, 2, 3); (11, 12, 13) ] +``` + It is commonly accepted to omit parentheses in pattern matching of tuples: ```fsharp @@ -143,7 +151,7 @@ let update model msg = In summary, prefer parenthesized tuple instantiations, but when using tuples for pattern matching or a return value, it is considered fine to avoid parentheses. -## Formatting application expressions +### Formatting application expressions When formatting a function or method application, arguments are provided on the same line when line-width allows: @@ -470,7 +478,7 @@ x &&& y // Bitwise and, also for working with “flags” enumeration x ^^^ y // Bitwise xor, also for working with “flags” enumeration ``` -## Formatting if expressions +### Formatting if expressions Indentation of conditionals depends on the size and complexity of the expressions that make them up. Write them on one line when: @@ -518,10 +526,10 @@ If a condition is long, the `then` keyword is still placed at the end of the exp ```fsharp if complexExpression a b && env.IsDevelopment() - || secondLongerExpression - aVeryLongparameterNameOne - aVeryLongparameterNameTwo - aVeryLongparameterNameThree then + || someFunctionToCall + aVeryLongParameterNameOne + aVeryLongParameterNameTwo + aVeryLongParameterNameThree then e1 else e2 @@ -532,10 +540,10 @@ It is, however, better style to refactor long conditions to a let binding or sep ```fsharp let performAction = complexExpression a b && env.IsDevelopment() - || secondLongerExpression - aVeryLongparameterNameOne - aVeryLongparameterNameTwo - aVeryLongparameterNameThree + || someFunctionToCall + aVeryLongParameterNameOne + aVeryLongParameterNameTwo + aVeryLongParameterNameThree if performAction then e1 @@ -543,7 +551,7 @@ else e2 ``` -## Formatting union case expressions +### Formatting union case expressions Applying discriminated union cases follows the same rules as function and method applications. That is, because the name is capitalized, code formatters will remove a space before a tuple: @@ -567,7 +575,7 @@ let tree1 = ) ``` -## Formatting list and array expressions +### Formatting list and array expressions Write `x :: l` with spaces around the `::` operator (`::` is an infix operator, hence surrounded by spaces). @@ -657,7 +665,7 @@ let daysOfWeek' includeWeekend = In some cases, `do...yield` may aid in readability. These cases, though subjective, should be taken into consideration. -## Formatting record expressions +### Formatting record expressions Short records can be written in one line: @@ -696,7 +704,7 @@ let rainbow = The same rules apply for list and array elements. -## Formatting copy-and-update record expressions +### Formatting copy-and-update record expressions A copy-and-update record expression is still a record, so similar guidelines apply. @@ -805,7 +813,7 @@ let rec sizeLambda acc = | Var v -> succ acc ``` -## Formatting try/with expressions +### Formatting try/with expressions Pattern matching on the exception type should be indented at the same level as `with`. @@ -900,7 +908,7 @@ let comparer = ## Formatting declarations -Top-level declarations should be documented and formatted consistently. +This section discusses formatting declarations of different kinds. ### Add blank lines between declarations @@ -1108,7 +1116,7 @@ let (!>) x f = f x For any custom operator that starts with `*` and that has more than one character, you need to add a white space to the beginning of the definition to avoid a compiler ambiguity. Because of this, we recommend that you simply surround the definitions of all operators with a single white-space character. -## Formatting record declarations +### Formatting record declarations For record declarations, indent `{` in type definition by four spaces, start the field list on the same line and align any members with the `{` token: @@ -1179,7 +1187,7 @@ type MyRecord = interface IMyInterface ``` -## Formatting discriminated union declarations +### Formatting discriminated union declarations For discriminated union declarations, indent `|` in type definition by four spaces: @@ -1253,7 +1261,7 @@ let MyUrl = "www.mywebsitethatiamworkingwith.com" Avoid placing the attribute on the same line as the value. -## Formatting module declarations +### Formatting module declarations Code in a local module must be indented relative to the module, but code in a top-level module should not be indented. Namespace elements do not have to be indented. @@ -1330,8 +1338,51 @@ async { } ``` +### Formatting computation expression operations + +When creating custom operations for [computation expressions](../language-reference/computation-expressions.md), it is recommended to use camelCase naming: + +```fsharp +type MathBuilder () = + member _.Yield _ = 0 + + [] + member _.AddOne (state: int) = + state + 1 + + [] + member _.SubtractOne (state: int) = + state - 1 + + [] + member _.DivideBy (state: int, divisor: int) = + state / divisor + + [] + member _.MultiplyBy (state: int, factor: int) = + state * factor + +let math = MathBuilder() + +// 10 +let myNumber = + math { + addOne + addOne + addOne + subtractOne + divideBy 2 + multiplyBy 10 + } +``` + +The domain that's being modeled should ultimately drive the naming convention. +If it is idiomatic to use a different convention, that convention should be used instead. + ## Formatting types and type annotations +This section discusses formatting types and type annotations. This includes formatting signature files with the `.fsi` extension. + ### For types, prefer prefix syntax for generics (`Foo`), with some specific exceptions F# allows both postfix style of writing generic types (for example, `int list`) as well as the prefix style (for example, `list`). @@ -1399,7 +1450,7 @@ type C() = member _.SomeMethodBad(x: int): int = 1 ``` -## Formatting types in signatures +### Formatting types in signatures When writing full function types in signatures, it is sometimes necessary to split the arguments over multiple lines. For a tupled function the arguments are separated by `*`, @@ -1561,44 +1612,3 @@ type MyRecord = ``` When applied to a parameter, they must be on the same line and separated by a `;` separator. - -## Formatting computation expression operations - -When creating custom operations for [computation expressions](../language-reference/computation-expressions.md), it is recommended to use camelCase naming: - -```fsharp -type MathBuilder () = - member _.Yield _ = 0 - - [] - member _.AddOne (state: int) = - state + 1 - - [] - member _.SubtractOne (state: int) = - state - 1 - - [] - member _.DivideBy (state: int, divisor: int) = - state / divisor - - [] - member _.MultiplyBy (state: int, factor: int) = - state * factor - -let math = MathBuilder() - -// 10 -let myNumber = - math { - addOne - addOne - addOne - subtractOne - divideBy 2 - multiplyBy 10 - } -``` - -The domain that's being modeled should ultimately drive the naming convention. -If it is idiomatic to use a different convention, that convention should be used instead. From 92ac8adeed45535b0c7c398c18b1fc603630edf5 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 14 Sep 2021 12:22:35 +0100 Subject: [PATCH 9/9] Update formatting.md --- docs/fsharp/style-guide/formatting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 319cb65df8ff1..667637f6bd165 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -1,7 +1,7 @@ --- title: F# code formatting guidelines description: Learn guidelines for formatting F# code. -ms.date: 08/31/2020 +ms.date: 09/14/2021 --- # F# code formatting guidelines