diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md index 433bca3ce73da..667637f6bd165 100644 --- a/docs/fsharp/style-guide/formatting.md +++ b/docs/fsharp/style-guide/formatting.md @@ -1,493 +1,464 @@ --- 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 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.md) and [Component design guidelines](component-design-guidelines.md), 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 + +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. + +### Use consistent indentation + +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. **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. +### Use an automatic code formatter -### Formatting operators in arithmetic expressions +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. -Always use white space around binary arithmetic expressions: +We strongly recommend the use of this code formatter. Within F# teams, code formatting specifications should be agreed and codified in terms +of an agreed settings file for the code formatter checked into the team repository. -```fsharp -let subtractThenAdd x = x - 1 + 3 -``` +### Avoid formatting that is sensitive to name length -Unary `-` operators should always be immediately followed by the value they are negating: +Seek to avoid indentation and alignment that is sensitive to naming: ```fsharp // OK -let negate x = -x +let myLongValueName = + someExpression + |> anotherExpression // Bad -let negateBad x = - x +let myLongValueName = someExpression + |> anotherExpression ``` -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 +This is sometimes called “vanity alignment” or “vanity indentation”. The primary reasons for avoiding this are: -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 `-`. +* Important code is moved far to the right +* There is less width left for the actual code +* Renaming can break the alignment -### Surround a custom operator definition with white space +### Avoid extraneous white space -Always use white space to surround an operator definition: +Avoid extraneous white space in F# code, except where described in this style guide. ```fsharp // OK -let ( !> ) x f = f x +spam (ham 1) -// Bad -let (!>) x f = f x +// Not OK +spam ( ham 1 ) ``` -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. - -### Surround function parameter arrows with white space +## Formatting comments -When defining the signature of a function, use white space around the `->` symbol: +Prefer multiple double-slash comments over block comments. ```fsharp -// OK -type MyFun = int -> int -> string +// Prefer this style of comments when you want +// to express written ideas on multiple lines. -// Bad -type MyFunBad = int->int->string +(* + Block comments can be used, but use sparingly. + They are useful when eliding code sections. +*) ``` -### Surround function arguments with white space - -When defining a function, use white space around each argument. +Comments should capitalize the first letter and be well-formed phrases or sentences. ```fsharp -// OK -let myFun (a: decimal) b c = a + b + c +// A good comment +let f x = x + 1 // Increment by one. -// Bad -let myFunBad (a:decimal)(b)c = a + b + c +// A poor comment +let f x = x + 1 // plus one ``` -### Avoid name-sensitive alignments +For formatting XML doc comments, see "Formatting declarations" below. -In general, seek to avoid indentation and alignment that is sensitive to naming: +## Formatting expressions -```fsharp -// OK -let myLongValueName = - someExpression - |> anotherExpression +This section discusses formatting expressions of different kinds. +### Formatting string expressions -// Bad -let myLongValueName = someExpression - |> anotherExpression +String literals and interpolated strings can just be left on a single line, regardless of how long the line is. + +```fsharp +let serviceStorageConnection = + $"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}" ``` -This is sometimes called “vanity alignment” or “vanity indentation”. The primary reasons for avoiding this are: +Multi-line interpolated expressions are discouraged. Instead, bind the expression result to a value and use that in the interpolated string. -* Important code is moved far to the right -* There is less width left for the actual code -* Renaming can break the alignment +### Formatting tuple expressions -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: +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 -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 +let pair = (1, 2) +let triples = [ (1, 2, 3); (11, 12, 13) ] ``` -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): +It is commonly accepted to omit parentheses in pattern matching of tuples: ```fsharp // OK -async { - let! foo = - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog - do! - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog -} +let (x, y) = z +let x, y = z -// Bad - notice the "do!" expression is indented two spaces more than the `let!` expression -async { - let! foo = - fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog - do! fooBarBaz - |> loremIpsumDolorSitAmet - |> theQuickBrownFoxJumpedOverTheLazyDog -} +// OK +match x, y with +| 1, _ -> 0 +| x, 1 -> 0 +| x, y -> 1 ``` -### 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. +It is also commonly accepted to omit parentheses if the tuple is the return value of a function: ```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 - - let longFunctionWithLongTupleParameterAndReturnType - ( - aVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse - ) : ReturnType = - // ... the body of the method follows +// OK +let update model msg = + match msg with + | 1 -> model + 1, [] + | _ -> model, [ msg ] ``` -This also applies to members, constructors, and parameters using tuples: - -```fsharp -type TM() = - member _.LongMethodWithLotsOfParameters - ( - aVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, - aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse - ) = - // ... the body of the method +In summary, prefer parenthesized tuple instantiations, but when using tuples for pattern matching or a return value, it is considered fine to avoid parentheses. -type TC - ( - aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse, - aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse, - aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse - ) = - // ... the body of the class follows -``` +### Formatting application expressions -If the parameters are currified, place the `=` character along with any return type on a new line: +When formatting a function or method application, arguments are provided on the same line when line-width allows: ```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 +// OK +someFunction1 x.IngredientName x.Quantity ``` -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. - -### Type annotations - -#### Right-pad value and function argument type annotations - -When defining values or arguments with type annotations, use white space after the `:` symbol, but not before: +Parentheses should be omitted unless arguments require them: ```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 +someFunction1 x.IngredientName -// Bad -let complexFunctionBad (a :int) (b :int) (c:int) = a + b + c -let expensiveToComputeBad1:int = 1 -let expensiveToComputeBad2 :int = 2 +// Not OK +someFunction1(x.IngredientName) ``` -#### Surround return type annotations with white space - -In function or member return type annotations, use white space before and after the `:` symbol: +In default formatting conventions, a space is added when applying lower-case functions to tupled or parenthesized arguments: ```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 +someFunction2 () -// Bad -let myFunBad (a: decimal) b c:decimal = a + b + c -let anotherFunBad (arg: int): unit = () -type C() = - member _.SomeMethod(x: int): int = 1 -``` +// OK +someFunction3 (x.Quantity1 + x.Quantity2) -### Formatting bindings +// Not OK, formatting tools will add the extra space by default +someFunction2() -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. +// Not OK, formatting tools will add the extra space by default +someFunction3(x.IngredientName, x.Quantity) +``` -For example, the following are non-compliant: +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 -let a = """ -foobar, long string -""" +// OK - Methods accepting parenthesize arguments are applied without a space +SomeClass.Invoke() -type File = - member this.SaveAsync(path: string) : Async = async { - // IO operation - return () - } +// OK - Methods accepting tuples are applied without a space +String.Format(x.IngredientName, x.Quantity) -let c = { - Name = "Bilbo" - Age = 111 - Region = "The Shire" -} +// Not OK, formatting tools will remove the extra space by default +SomeClass.Invoke () -let d = while f do - printfn "%A" x +// Not OK, formatting tools will remove the extra space by default +String.Format (x.IngredientName, x.Quantity) ``` -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, but formatting tools will reformat to the above +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. +let person = new Person(a1, a2) + +let myRegexMatch = Regex.Match(input, regex) + +let untypedRes = checker.ParseFile(file, source, opts) ``` -## Formatting string literals and interpolated strings - -String literals and interpolated strings can just be left on a single line, regardless of how long the line is. +If argument expressions are long, use newlines and indent one level, rather than indenting to the left-parenthesis. ```fsharp -let serviceStorageConnection = - $"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}" -``` - -Multi-line interpolated expressions are strongly discouraged. Instead, bind the expression result to a value and use that in the interpolated string. - -## Naming conventions - -### Use camelCase for class-bound, expression-bound, and pattern-bound values and functions +// 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 + """ +) + +Option.traverse( + create + >> Result.setError [ invalidHeader "Content-Checksum" ] +) +``` + +### Formatting pipeline expressions - let doSomething () = +When using multiple lines, pipeline `|>` operators should go underneath the expressions they operate on. - let firstResult = ... +```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 - let secondResult = ... +// 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 - member x.Result = doSomething() +// 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,123 +478,96 @@ 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 +### Formatting if 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)`. +Indentation of conditionals depends on the size and complexity of the expressions that make them up. +Write them on one line when: -It is commonly accepted to omit parentheses in pattern matching of tuples: +- `cond`, `e1`, and `e2` are short +- `e1` and `e2` are not `if/then/else` expressions themselves. ```fsharp -let (x, y) = z // Destructuring -let x, y = z // OK - -// OK -match x, y with -| 1, _ -> 0 -| x, 1 -> 0 -| x, y -> 1 +if cond then e1 else e2 ``` -It is also commonly accepted to omit parentheses if the tuple is the return value of a function: +If any of the expressions are multi-line or `if/then/else` expressions. ```fsharp -// OK -let update model msg = - match msg with - | 1 -> model + 1, [] - | _ -> model, [ msg ] +if cond then + e1 +else + e2 ``` -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: +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 -// 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 +if cond1 then e1 +elif cond2 then e2 +elif cond3 then e3 +else e4 ``` -When there is a single short union, you can omit the leading `|`. +If any of the conditions or expressions is multi-line, the entire `if/then/else` expression is multi-line: ```fsharp -type Address = Address of string +if cond1 then + e1 +elif cond2 then + e2 +elif cond3 then + e3 +else + e4 ``` -For a longer or multiline union, keep the `|`. +If a condition is long, the `then` keyword is still placed at the end of the expression. ```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 +if + complexExpression a b && env.IsDevelopment() + || someFunctionToCall + aVeryLongParameterNameOne + aVeryLongParameterNameTwo + aVeryLongParameterNameThree then + e1 + else + e2 ``` -You can also use triple-slash `///` comments. +It is, however, better style to refactor long conditions to a let binding or separate function: ```fsharp -type Foobar = - /// Code comment - | Foobar of int +let performAction = + complexExpression a b && env.IsDevelopment() + || someFunctionToCall + aVeryLongParameterNameOne + aVeryLongParameterNameTwo + aVeryLongParameterNameThree + +if performAction then + e1 +else + e2 ``` -## Formatting discriminated unions +### 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) -``` - -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)) +// 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), @@ -631,168 +575,33 @@ let tree1 = ) ``` -## Formatting record declarations +### Formatting list and array expressions + +Write `x :: l` with spaces around the `::` operator (`::` is an infix operator, hence surrounded by spaces). + +List and arrays declared on a single line should have a space after the opening bracket and before the closing bracket: + +```fsharp +let xs = [ 1; 2; 3 ] +let ys = [| 1; 2; 3; |] +``` -Indent `{` in type definition by four spaces and start the field list on the same line: +Always use at least one space between two distinct brace-like operators. For example, leave a space between a `[` and a `{`. ```fsharp // OK -type PostalAddress = - { Address: string - City: string - Zip: string } - member x.ZipAndCity = $"{x.Zip} {x.City}" +[ { 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 -type PostalAddress = - { Address: string - City: string - Zip: string } - member x.ZipAndCity = $"{x.Zip} {x.City}" - -// Unusual in F# -type PostalAddress = - { - Address: string - City: string - Zip: string - } -``` - -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 = - { - Address: string - City: string - Zip: string - } - member x.ZipAndCity = $"{x.Zip} {x.City}" - -type MyRecord = - { - SomeField: int - } - interface IMyInterface -``` - -## Formatting records - -Short records can be written in one line: - -```fsharp -let point = { X = 1.0; Y = 0.0 } -``` - -Records that are longer should use new lines for labels: - -```fsharp -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 - -```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 foo a = - a - |> Option.map (fun x -> - { - MyField = x - }) -``` - -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 -let point2 = { point with X = 1; Y = 2 } -``` - -Longer expressions should use new lines: - -```fsharp -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: - -```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 = "" - } - } -``` - -## Formatting lists and arrays - -Write `x :: l` with spaces around the `::` operator (`::` is an infix operator, hence surrounded by spaces). - -List and arrays declared on a single line should have a space after the opening bracket and before the closing bracket: - -```fsharp -let xs = [ 1; 2; 3 ] -let ys = [| 1; 2; 3; |] -``` - -Always use at least one space between two distinct brace-like operators. For example, leave a space between a `[` and a `{`. - -```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 } ] - -// 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. @@ -801,17 +610,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. @@ -858,90 +665,88 @@ let daysOfWeek' includeWeekend = In some cases, `do...yield` may aid in readability. These cases, though subjective, should be taken into consideration. -## Formatting if expressions +### Formatting record expressions -Indentation of conditionals depends on the size and complexity of the expressions that make them up. -Write them on one line when: - -- `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 +// OK +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 `{` and `}` on new lines with contents indented is possible, however code formatters may reformat this by default: ```fsharp -if cond1 then e1 -elif cond2 then e2 -elif cond3 then e3 -else e4 +// 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" + Lackeys = ["Zippy"; "George"; "Bungle"] + } ``` -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 +// OK +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 +// OK +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 +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 -let condition () = - complexExpression a b && env.IsDevelopment() - || secondLongerExpression - aVeryLongparameterNameOne - aVeryLongparameterNameTwo - aVeryLongparameterNameThree - """ -Multiline - string - """ - -if condition () then - e1 -else - e2 +// OK +let newState = + { state with + Foo = Some { F1 = 0; F2 = "" } } + +// Not OK, code formatters will reformat to the above by default +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. @@ -952,7 +757,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 @@ -962,18 +767,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, code formatters will reformat to the above by default +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 introduced by using 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,9 +801,11 @@ 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 +// OK let rec sizeLambda acc = function | Abs(x, body) -> sizeLambda (succ acc) body @@ -991,9 +813,7 @@ let rec sizeLambda acc = | Var v -> succ acc ``` -We do not recommend aligning arrows. - -## Formatting try/with expressions +### Formatting try/with expressions Pattern matching on the exception type should be indented at the same level as `with`. @@ -1040,323 +860,667 @@ 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, code formatters will reformat to the above by default +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) } +``` -// OK -sprintf "\t%s - %i\n\r" - x.IngredientName x.Quantity +## Formatting declarations -// OK -sprintf - "\t%s - %i\n\r" - x.IngredientName x.Quantity +This section discusses formatting declarations of different kinds. +### Add blank lines between declarations + +Separate top-level function and class definitions with a single blank line. For example: + +```fsharp // OK -let printVolumes x = - printf "Volume in liters = %f, in us pints = %f, in imperial = %f" - (convertVolumeToLiter x) - (convertVolumeUSPint x) - (convertVolumeImperialPint x) +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 ``` -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 a construct has XML doc comments, add a blank line before the comment. ```fsharp -let printListWithOffset a list1 = - List.iter - (fun elem -> - printfn $"A very long line to format the value: %d{a + elem}") - list1 +/// This is a function +let thisFunction() = + 1 + 1 + +/// This is another function, note the blank line before this line +let thisFunction() = + 1 + 1 ``` -If the lambda argument is the last argument in a function application, place all arguments until the arrow on the same line. +### 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 -Target.create "Build" (fun ctx -> - // code - // here - ()) +// OK +let a = + """ +foobar, long string +""" -let printListWithOffsetPiped a list1 = - list1 - |> List.iter (fun elem -> - printfn $"A very long line to format the value: %d{a + elem}") +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, code formatters will reformat to the above by default +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 myFun (a: decimal) (b: int) 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) + (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 +``` + +This also applies to members, constructors, and parameters using tuples: + +```fsharp +type TypeWithLongMethod() = + member _.LongMethodWithLotsOfParameters + ( + aVeryLongParam: AVeryLongTypeThatYouNeedToUse, + aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, + aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse + ) = + // ... the body of the method + +type TypeWithLongConstructor + ( + aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse, + aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse, + aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse + ) = + // ... the body of the class follows +``` + +If the parameters are curried, place the `=` character along with any return type on a new line: + +```fsharp +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 +``` + +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 + +Optionally use white space to surround an operator definition: + +```fsharp +// OK +let ( !> ) x f = f x + +// OK +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 + +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 +// OK +type PostalAddress = + { Address: string + City: string + Zip: string } + member x.ZipAndCity = $"{x.Zip} {x.City}" +``` + +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: + +```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 + + /// The city + City: string + + /// The zip code + Zip: string + } + member x.ZipAndCity = $"{x.Zip} {x.City}" + +type MyRecord = + { + /// The record field + SomeField: int + } + interface IMyInterface +``` + +### Formatting discriminated union declarations + +For 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 multi-line union, keep the `|` and place each union field on a new line, with the separating `*` at the end of each line. + +```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 +``` + +When documentation comments are added, use an empty line before each `///` comment. + +```fsharp +/// The volume +type Volume = + + /// The volume in liters + | Liter of float + + /// The volume in fluid ounces + | FluidOunce of float + + /// The volume in imperial pints + | ImperialPint of float +``` + +### 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 +[] +let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__ + +[] +let MyUrl = "www.mywebsitethatiamworkingwith.com" +``` + +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. + +```fsharp +// A is a top-level module. +module A + +let function1 a b = a - b * b +``` + +```fsharp +// A1 and A2 are local modules. +module A1 = + let function1 a b = a * a + b * b + +module A2 = + let function2 a b = a * a - b * b +``` + +### Formatting do declarations + +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 +// 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 +} ``` -Treat match lambda's in a similar fashion. +### Formatting computation expression operations + +When creating custom operations for [computation expressions](../language-reference/computation-expressions.md), it is recommended to use camelCase naming: ```fsharp -functionName arg1 arg2 arg3 (function - | Choice1of2 x - | Choice2of2 y) -``` +type MathBuilder () = + member _.Yield _ = 0 -Unless there are many leading or multiline arguments before the lambda. In that case, indent all arguments with one scope. + [] + member _.AddOne (state: int) = + state + 1 -```fsharp -functionName - arg1 - arg2 - arg3 - (fun arg4 -> - bodyExpr) + [] + member _.SubtractOne (state: int) = + state - 1 -functionName - arg1 - arg2 - arg3 - (function - | Choice1of2 x - | Choice2of2 y) -``` + [] + member _.DivideBy (state: int, divisor: int) = + state / divisor -If the body of a lambda expression is multiple lines long, you should consider refactoring it into a locally-scoped function. + [] + member _.MultiplyBy (state: int, factor: int) = + state * factor -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. +let math = MathBuilder() -```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}") +// 10 +let myNumber = + math { + addOne + addOne + addOne + subtractOne + divideBy 2 + multiplyBy 10 + } +``` -list1 -|> List.fold - // one indent from the function name - someLongParam - anotherLongParam +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. -// With 2 spaces indentation -list1 -|> List.fold - // one indent from the function name - someLongParam - anotherLongParam +## Formatting types and type annotations -list1 -|> List.iter (fun elem -> - // one indent starting from the pipe - printfn $"A very long line to format the value: %d{elem}") -``` +This section discusses formatting types and type annotations. This includes formatting signature files with the `.fsi` extension. -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. +### For types, prefer prefix syntax for generics (`Foo`), with some specific exceptions -```fsharp -let myFunction (a: int, b: string, c: int, d: bool) = - () +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: -myFunction( - 478815516, - "A very long string making all of this multi-line", - 1515, - false -) -``` +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`. -### Formatting infix operators +For all other types, use the prefix form. -Separate operators by spaces. Obvious exceptions to this rule are the `!` and `.` operators. +### Formatting function types -Infix expressions are OK to lineup on same column: +When defining the signature of a function, use white space around the `->` symbol: ```fsharp -acc + -(sprintf "\t%s - %i\n\r" - x.IngredientName x.Quantity) +// OK +type MyFun = int -> int -> string -let function1 arg1 arg2 arg3 arg4 = - arg1 + arg2 + - arg3 + arg4 +// Bad +type MyFunBad = int->int->string ``` -### Formatting pipeline operators or mutable assignments +### Formatting value and argument type annotations -Pipeline `|>` operators should go underneath the expressions they operate on. +When defining values or arguments with type annotations, use white space after the `:` symbol, but not before: ```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 -``` +// OK +let complexFunction (a: int) (b: int) c = a + b + c -This also applies to mutable setters: +let simpleValue: int = 0 // Type annotation for let-bound value -```fsharp -// Preferred approach -ctx.Response.Headers.[HeaderNames.ContentType] <- - Constants.jsonApiMediaType |> StringValues -ctx.Response.Headers.[HeaderNames.ContentLength] <- - bytes.Length |> string |> StringValues +type C() = + member _.Property: int = 1 -// Not OK -ctx.Response.Headers.[HeaderNames.ContentType] <- Constants.jsonApiMediaType - |> StringValues -ctx.Response.Headers.[HeaderNames.ContentLength] <- bytes.Length - |> string - |> StringValues +// Bad +let complexFunctionPoorlyAnnotated (a :int) (b :int) (c:int) = a + b + c +let simpleValuePoorlyAnnotated1:int = 1 +let simpleValuePoorlyAnnotated2 :int = 2 ``` -### Formatting modules +### Formatting return type annotations -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. +In function or member return type annotations, use white space before and after the `:` symbol: ```fsharp -// A is a top-level module. -module A - -let function1 a b = a - b * b -``` +// OK +let myFun (a: decimal) b c : decimal = a + b + c // Type annotation for the return type of a function -```fsharp -// A1 and A2 are local modules. -module A1 = - let function1 a b = a * a + b * b +let anotherFun (arg: int) : unit = () // Type annotation for return type of a function -module A2 = - let function2 a b = a * a - b * b -``` +type C() = + member _.SomeMethod(x: int) : int = 1 // Type annotation for return type of a member -### Formatting object expressions and interfaces +// Bad +let myFunBad (a: decimal) b c:decimal = a + b + c -Object expressions and interfaces should be aligned in the same way with `member` being indented after four spaces. +let anotherFunBad (arg: int): unit = () -```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) } +type C() = + member _.SomeMethodBad(x: int): int = 1 ``` -### Formatting white space in expressions +### Formatting types in signatures -Avoid extraneous white space in F# expressions. +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 -// OK -spam (ham.[1]) - -// Not OK -spam ( ham.[ 1 ] ) +let SampleTupledFunction(arg1, arg2, arg3, arg4) = ... ``` -Named arguments should also not have space surrounding the `=`: +In the corresponding signature file (`.fsi` extension) the function can be formatted as follows when +multi-line formatting is required: ```fsharp -// OK -let makeStreamReader x = new System.IO.StreamReader(path=x) - -// Not OK -let makeStreamReader x = new System.IO.StreamReader(path = x) +val SampleTupledFunction: + arg1: string * + arg2: string * + arg3: int * + arg4: int + -> int list ``` -### Formatting constructors, static members, and member invocations - -If the expression is short, separate arguments with spaces and keep it in one line. +Likewise consider a curried function: ```fsharp -let person = new Person(a1, a2) - -let myRegexMatch = Regex.Match(input, regex) - -let untypedRes = checker.ParseFile(file, source, opts) +let SampleCurriedFunction arg1 arg2 arg3 arg4 = ... ``` -If the expression is long, use newlines and indent one scope, rather than indenting to the bracket. +In the corresponding signature file the `->` are placed at the start of each line: ```fsharp -let person = - new Person( - argument1, - argument2 - ) +val SampleCurriedFunction: + arg1: string + -> arg2: string + -> arg3: int + -> arg4: int + -> int list +``` -let myRegexMatch = - Regex.Match( - "my longer input string with some interesting content in it", - "myRegexPattern" - ) +Likewise consider a function that takes a mix of curried and tupled arguments: -let untypedRes = - checker.ParseFile( - fileName, - sourceText, - parsingOptionsWithDefines - ) +```fsharp +// Typical call syntax: +let SampleMixedFunction + (arg1, arg2) + (arg3, arg4, arg5) + (arg6, arg7) + (arg8, arg9, arg10) = .. ``` -The same rules apply even if there is only a single multiline argument. +In the corresponding signature file the `->` are placed at the end of each argument except the last: ```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 - """ -) - -Option.traverse( - create - >> Result.setError [ invalidHeader "Content-Checksum" ] -) +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: @@ -1448,61 +1612,3 @@ 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: - -```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.