# F# code formatting guidelines

• More legible
• In accordance with conventions applied by formatting tools in Visual Studio Code and other editors
• Similar to other code online

See also Coding conventions and Component design guidelines, which also covers naming conventions.

## Automatic code formatting

The Fantomas code formatter is the F# community standard tool for automatic code formatting. The default 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 an agreed settings file for the code formatter checked into the team repository.

## General rules for formatting

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.

### Use spaces not tabs

When indentation is required, you must use spaces, not tabs. F# code doesn't 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.

### Avoid formatting that is sensitive to name length

Seek to avoid indentation and alignment that is sensitive to naming:

// ✔️ OK
let myLongValueName =
someExpression
|> anotherExpression

// ❌ Not OK
let myLongValueName = someExpression
|> anotherExpression

// ✔️ OK
let myOtherVeryLongValueName =
match
someVeryLongExpressionWithManyParameters
parameter1
parameter2
parameter3
with
| Some _ -> ()
| ...

// ❌ Not OK
let myOtherVeryLongValueName =
match someVeryLongExpressionWithManyParameters parameter1
parameter2
parameter3 with
| Some _ -> ()
| ...

// ❌ Still Not OK
let myOtherVeryLongValueName =
match someVeryLongExpressionWithManyParameters
parameter1
parameter2
parameter3 with
| Some _ -> ()
| ...


The primary reasons for avoiding this are:

• Important code is moved far to the right
• There's less width left for the actual code
• Renaming can break the alignment

### Avoid extraneous white space

Avoid extraneous white space in F# code, except where described in this style guide.

// ✔️ OK
spam (ham 1)

// ❌ Not OK
spam ( ham 1 )


// Prefer this style of comments when you want
// to express written ideas on multiple lines.

(*
Block comments can be used, but use sparingly.
They are useful when eliding code sections.
*)


Comments should capitalize the first letter and be well-formed phrases or sentences.

// ✔️ A good comment.
let f x = x + 1 // Increment by one.

let f x = x + 1 // plus one


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.

let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"  Multi-line interpolated expressions are discouraged. Instead, bind the expression result to a value and use that in the interpolated string. ### 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). // ✔️ OK let pair = (1, 2) let triples = [ (1, 2, 3); (11, 12, 13) ]  It's commonly accepted to omit parentheses in pattern matching of tuples: // ✔️ OK let (x, y) = z let x, y = z // ✔️ OK match x, y with | 1, _ -> 0 | x, 1 -> 0 | x, y -> 1  It's also commonly accepted to omit parentheses if the tuple is the return value of a function: // ✔️ 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's considered fine to avoid parentheses. ### Formatting application expressions When formatting a function or method application, arguments are provided on the same line when line-width allows: // ✔️ OK someFunction1 x.IngredientName x.Quantity  Omit parentheses unless arguments require them: // ✔️ OK someFunction1 x.IngredientName // ❌ Not preferred - parentheses should be omitted unless required someFunction1 (x.IngredientName) // ✔️ OK - parentheses are required someFunction1 (convertVolumeToLiter x)  Don't omit spaces when invoking with multiple curried arguments: // ✔️ OK someFunction1 (convertVolumeToLiter x) (convertVolumeUSPint x) someFunction2 (convertVolumeToLiter y) y someFunction3 z (convertVolumeUSPint z) // ❌ Not preferred - spaces should not be omitted between arguments someFunction1(convertVolumeToLiter x)(convertVolumeUSPint x) someFunction2(convertVolumeToLiter y) y someFunction3 z(convertVolumeUSPint z)  In default formatting conventions, a space is added when applying lower-case functions to tupled or parenthesized arguments (even when a single argument is used): // ✔️ OK someFunction2 () // ✔️ OK someFunction3 (x.Quantity1 + x.Quantity2) // ❌ 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)  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: // ✔️ 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) // ❌ Not OK, formatting tools will remove the extra space by default SomeClass.Invoke () // ❌ Not OK, 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: // ✔️ OK someFunction2 x.IngredientName x.Quantity // ✔️ OK someFunction3 x.IngredientName1 x.Quantity2 x.IngredientName2 x.Quantity2 // ✔️ OK someFunction4 x.IngredientName1 x.Quantity2 x.IngredientName2 x.Quantity2 // ✔️ OK someFunction5 (convertVolumeToLiter x) (convertVolumeUSPint x) (convertVolumeImperialPint x)  When the function takes a single multi-line tupled argument, place each argument on a new line: // ✔️ OK someTupledFunction ( 478815516, "A very long string making all of this multi-line", 1515, false ) // 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. // ✔️ OK let person = new Person(a1, a2) // ✔️ OK let myRegexMatch = Regex.Match(input, regex) // ✔️ OK let untypedRes = checker.ParseFile(file, source, opts)  If argument expressions are long, use newlines and indent one level, rather than indenting to the left-parenthesis. // ✔️ OK let person = new Person( argument1, argument2 ) // ✔️ OK let myRegexMatch = Regex.Match( "my longer input string with some interesting content in it", "myRegexPattern" ) // ✔️ OK let untypedRes = checker.ParseFile( fileName, sourceText, parsingOptionsWithDefines ) // ❌ Not OK, formatting tools will reformat to the above let person = new Person(argument1, argument2) // ❌ Not OK, formatting tools will reformat to the above let untypedRes = checker.ParseFile(fileName, sourceText, parsingOptionsWithDefines)  The same rules apply even if there is only a single multi-line argument, including multi-line strings: // ✔️ OK 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 When using multiple lines, pipeline |> operators should go underneath the expressions they operate on. // ✔️ 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, add a line break after "=" and put multi-line pipelines on multiple lines. 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  ### Formatting lambda expressions 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: // ✔️ OK let printListWithOffset a list1 = List.iter (fun elem -> printfn$"A very long line to format the value: %d{a + elem}")
list1


If the lambda argument is the last argument in a function application, place all arguments until the arrow on the same line.

// ✔️ OK
Target.create "Build" (fun ctx ->
// code
// here
())

// ✔️ OK
let printListWithOffsetPiped a list1 =
list1
|> List.map (fun x -> x + 1)
|> List.iter (fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")  Treat match lambda's in a similar fashion. // ✔️ OK functionName arg1 arg2 arg3 (function | Choice1of2 x -> 1 | Choice2of2 y -> 2)  When there are many leading or multi-line arguments before the lambda indent all arguments with one level. // ✔️ OK functionName arg1 arg2 arg3 (fun arg4 -> bodyExpr) // ✔️ OK functionName arg1 arg2 arg3 (function | Choice1of2 x -> 1 | Choice2of2 y -> 2)  If the body of a lambda expression is multiple lines long, you should consider refactoring it into a locally scoped function. When pipelines include lambda expressions, each lambda expression is typically the last argument at each stage of the pipeline: // ✔️ OK, 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}")

// ✔️ OK, 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}")  ### Formatting arithmetic and binary expressions Always use white space around binary arithmetic expressions: // ✔️ OK let subtractThenAdd x = x - 1 + 3  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 negate: // ✔️ OK let negate x = -x // ❌ Not OK let negateBad x = - x  Adding a white-space character after the - operator can lead to confusion for others. Separate binary operators by spaces. Infix expressions are OK to lineup on same column: // ✔️ OK let function1 () = acc + (someFunction x.IngredientName x.Quantity) // ✔️ OK let function1 arg1 arg2 arg3 arg4 = arg1 + arg2 + arg3 + arg4  This rule also applies to units of measures in types and constant annotations: // ✔️ OK type Test = { WorkHoursPerWeek: uint<hr / (staff weeks)> } static member create = { WorkHoursPerWeek = 40u<hr / (staff weeks)> } // ❌ Not OK type Test = { WorkHoursPerWeek: uint<hr/(staff weeks)> } static member create = { WorkHoursPerWeek = 40u<hr/(staff weeks)> }  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. // ✔️ OK x |> f // Forward pipeline f >> g // Forward composition x |> ignore // Discard away a value x + y // Overloaded addition (including string concatenation) x - y // Overloaded subtraction x * y // Overloaded multiplication x / y // Overloaded division x % y // Overloaded modulus x && y // Lazy/short-cut "and" x || y // Lazy/short-cut "or" x <<< y // Bitwise left shift x >>> y // Bitwise right shift x ||| y // Bitwise or, also for working with “flags” enumeration x &&& y // Bitwise and, also for working with “flags” enumeration x ^^^ y // Bitwise xor, also for working with “flags” enumeration  ### Formatting range operator expressions Only add spaces around the .. when all expressions are non-atomic. Integers and single word identifiers are considered atomic. // ✔️ OK let a = [ 2..7 ] // integers let b = [ one..two ] // identifiers let c = [ ..9 ] // also when there is only one expression let d = [ 0.7 .. 9.2 ] // doubles let e = [ 2L .. number / 2L ] // complex expression let f = [| A.B .. C.D |] // identifiers with dots let g = [ .. (39 - 3) ] // complex expression let h = [| 1 .. MyModule.SomeConst |] // not all expressions are atomic for x in 1..2 do printfn " x = %d" x let s = seq { 0..10..100 } // ❌ Not OK let a = [ 2 .. 7 ] let b = [ one .. two ]  These rules also apply to slicing: // ✔️ OK arr[0..10] list[..^1]  ### 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: • cond, e1, and e2 are short • e1 and e2 are not if/then/else expressions themselves. // ✔️ OK if cond then e1 else e2  If the else expression is absent, it's recommended to never write the entire expression in one line. This is to differentiate the imperative code from the functional. // ✔️ OK if a then () // ❌ Not OK, code formatters will reformat to the above by default if a then ()  If any of the expressions are multi-line or if/then/else expressions. // ✔️ OK if cond then e1 else e2  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. // ✔️ OK if cond1 then e1 elif cond2 then e2 elif cond3 then e3 else e4  If any of the conditions or expressions is multi-line, the entire if/then/else expression is multi-line: // ✔️ OK if cond1 then e1 elif cond2 then e2 elif cond3 then e3 else e4  If a condition is multiline or exceeds the default tolerance of the single-line, the condition expression should use one indentation and a new line. The if and then keyword should align when encapsulating the long condition expression. // ✔️ OK, but better to refactor, see below if complexExpression a b && env.IsDevelopment() || someFunctionToCall aVeryLongParameterNameOne aVeryLongParameterNameTwo aVeryLongParameterNameThree then e1 else e2 // ✔️The same applies to nested elif or else if expressions if a then b elif someLongFunctionCall argumentOne argumentTwo argumentThree argumentFour then c else if someOtherLongFunctionCall argumentOne argumentTwo argumentThree argumentFour then d  It is, however, better style to refactor long conditions to a let binding or separate function: // ✔️ OK let performAction = complexExpression a b && env.IsDevelopment() || someFunctionToCall aVeryLongParameterNameOne aVeryLongParameterNameTwo aVeryLongParameterNameThree if performAction then e1 else e2  ### 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: // ✔️ OK let opt = Some("A", 1) // OK, but code formatters will remove the space let opt = Some ("A", 1)  Like function applications, constructions that split across multiple lines should use indentation: // ✔️ OK let tree1 = BinaryNode( BinaryNode (BinaryValue 1, BinaryValue 2), BinaryNode (BinaryValue 3, BinaryValue 4) )  ### 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: // ✔️ OK let xs = [ 1; 2; 3 ] // ✔️ OK 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 {. // ✔️ OK [ { 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 [{ 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. Lists and arrays that split across multiple lines follow a similar rule as records do: // ✔️ OK 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 |] |]  And as with records, declaring the opening and closing brackets on their own line will make moving code around and piping into functions easier. When generating arrays and lists programmatically, prefer -> over do ... yield when a value is always generated: // ✔️ OK let squares = [ for x in 1..10 -> x * x ] // ❌ Not preferred, use "->" when a value is always generated let squares' = [ for x in 1..10 do yield x * x ]  Older versions of F# required specifying yield in situations where data may be generated conditionally, or there may be consecutive expressions to be evaluated. Prefer omitting these yield keywords unless you must compile with an older F# language version: // ✔️ OK let daysOfWeek includeWeekend = [ "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" if includeWeekend then "Saturday" "Sunday" ] // ❌ Not preferred - omit yield instead let daysOfWeek' includeWeekend = [ yield "Monday" yield "Tuesday" yield "Wednesday" yield "Thursday" yield "Friday" if includeWeekend then yield "Saturday" yield "Sunday" ]  In some cases, do...yield may aid in readability. These cases, though subjective, should be taken into consideration. ### Formatting record expressions Short records can be written in one line: // ✔️ OK let point = { X = 1.0; Y = 0.0 }  Records that are longer should use new lines for labels: // ✔️ OK let rainbow = { Boss = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }  Long record field expressions should use a new line and have one indent from the opening {: { A = a B = someFunctionCall arg1 arg2 // ... argX C = c }  Placing the { and } on new lines with contents indented is possible, however code formatters may reformat this by default: // ✔️ OK let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] } // ❌ Not preferred, code formatters will reformat to the above by default let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }  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: // ✔️ OK let point2 = { point with X = 1; Y = 2 }  Longer expressions should use new lines: // ✔️ OK let rainbow2 = { rainbow with Boss = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }  You may want to dedicate separate lines for the braces and indent one scope to the right with the expression, however code formatters may reformat it. In some special cases, such as wrapping a value with an optional without parentheses, you may need to keep a brace on one line: // ✔️ 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 = "" } }  ### 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. // ✔️ OK match l with | { him = x; her = "Posh" } :: tail -> x | _ :: tail -> findDavid tail | [] -> failwith "Couldn't find David" // ❌ Not OK, code formatters will reformat to the above by default match l with | { him = x; her = "Posh" } :: tail -> x | _ :: tail -> findDavid tail | [] -> failwith "Couldn't find David"  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/|. // ✔️ OK match lam with | Var v -> 1 | Abs(x, body) -> 1 + sizeLambda body | App(lam1, lam2) -> sizeLambda lam1 + sizeLambda lam2  Similar to large if conditions, if a match expression is multiline or exceeds the default tolerance of the single-line, the match expression should use one indentation and a new line. The match and with keyword should align when encapsulating the long match expression. // ✔️ OK, but better to refactor, see below match complexExpression a b && env.IsDevelopment() || someFunctionToCall aVeryLongParameterNameOne aVeryLongParameterNameTwo aVeryLongParameterNameThree with | X y -> y | _ -> 0  It is, however, better style to refactor long match expressions to a let binding or separate function: // ✔️ OK let performAction = complexExpression a b && env.IsDevelopment() || someFunctionToCall aVeryLongParameterNameOne aVeryLongParameterNameTwo aVeryLongParameterNameThree match performAction with | X y -> y | _ -> 0  Aligning the arrows of a pattern match should be avoided. // ✔️ 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 introduced by using the keyword function should indent one level from the start of the previous line: // ✔️ OK lambdaList |> List.map (function | Abs(x, body) -> 1 + sizeLambda 0 body | App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2 | Var v -> 1)  The use of function in functions defined by let or let rec should in general be avoided in favor of a match. If used, the pattern rules should align with the keyword function: // ✔️ OK let rec sizeLambda acc = function | Abs(x, body) -> sizeLambda (succ acc) body | App(lam1, lam2) -> sizeLambda (sizeLambda acc lam1) lam2 | Var v -> succ acc  ### Formatting try/with expressions Pattern matching on the exception type should be indented at the same level as with. // ✔️ OK try if System.DateTime.Now.Second % 3 = 0 then raise (new System.Exception()) else raise (new System.ApplicationException()) with | :? System.ApplicationException -> printfn "A second that was not a multiple of 3" | _ -> printfn "A second that was a multiple of 3"  Add a | for each clause, except when there is only a single clause: // ✔️ OK try persistState currentState with ex -> printfn "Something went wrong: %A" ex // ✔️ OK try persistState currentState with :? System.ApplicationException as ex -> printfn "Something went wrong: %A" ex // ❌ Not OK, see above for preferred formatting try persistState currentState with | ex -> printfn "Something went wrong: %A" ex // ❌ Not OK, see above for preferred formatting try persistState currentState with | :? System.ApplicationException as ex -> printfn "Something went wrong: %A" ex  ### Formatting named arguments Named arguments should have spaces surrounding the =: // ✔️ OK let makeStreamReader x = new System.IO.StreamReader(path = x) // ❌ Not OK, spaces are necessary around '=' for named arguments let makeStreamReader x = new System.IO.StreamReader(path=x)  When pattern matching using discriminated unions, named patterns are formatted similarly, for example. type Data = | TwoParts of part1: string * part2: string | OnePart of part1: string // ✔️ OK let examineData x = match data with | OnePartData(part1 = p1) -> p1 | TwoPartData(part1 = p1; part2 = p2) -> p1 + p2 // ❌ Not OK, spaces are necessary around '=' for named pattern access let examineData x = match data with | OnePartData(part1=p1) -> p1 | TwoPartData(part1=p1; part2=p2) -> p1 + p2  ### 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. // ✔️ 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  ### Formatting object expressions Object expression members should be aligned with member being indented by one level. // ✔️ OK let comparer = { new IComparer<string> 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 index/slice expressions Index expressions shouldn't contain any spaces around the opening and closing brackets. // ✔️ OK let v = expr[idx] let y = myList[0..1] // ❌ Not OK let v = expr[ idx ] let y = myList[ 0 .. 1 ]  This also applies for the older expr.[idx] syntax. // ✔️ OK let v = expr.[idx] let y = myList.[0..1] // ❌ Not OK let v = expr.[ idx ] let y = myList.[ 0 .. 1 ]  ### Formatting quoted expressions The delimiter symbols (<@ , @>, <@@, @@>) should be placed on separate lines if the quoted expression is a multi-line expression. // ✔️ OK <@ let f x = x + 10 f 20 @> // ❌ Not OK <@ let f x = x + 10 f 20 @>  In single-line expressions the delimiter symbols should be placed on the same line as the expression itself. // ✔️ OK <@ 1 + 1 @> // ❌ Not OK <@ 1 + 1 @>  ## Formatting declarations 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: // ✔️ OK 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. // ✔️ OK /// 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: // ✔️ OK let a = """ foobar, long string """ // ✔️ OK type File = member this.SaveAsync(path: string) : Async<unit> = async { // IO operation return () } // ✔️ OK let c = { Name = "Bilbo" Age = 111 Region = "The Shire" } // ✔️ OK let d = while f do printfn "%A" x  The following are non-compliant: // ❌ Not OK, code formatters will reformat to the above by default let a = """ foobar, long string """ type File = member this.SaveAsync(path: string) : Async<unit> = 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: // ✔️ OK /// 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. // ✔️ 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. // ✔️ 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: // ✔️ OK type TypeWithLongMethod() = member _.LongMethodWithLotsOfParameters ( aVeryLongParam: AVeryLongTypeThatYouNeedToUse, aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse, aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse ) = // ... the body of the method // ✔️ OK 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: // ✔️ OK 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: // ✔️ 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: // ✔️ OK type PostalAddress = { Address: string City: string Zip: string } member x.ZipAndCity ="{x.Zip} {x.City}"


Don't place the { at the end of the type declaration line, and don't use with/end for members, which are redundant.

// ❌ Not OK, code formatters will reformat to the above by default
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: // ✔️ OK 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're declaring interface implementations or members on the record:

// ✔️ OK
{

/// 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:

// ✔️ 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 |.

// ✔️ OK


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.

// ✔️ OK
[<NoEquality; NoComparison>]
type SynBinding =
| SynBinding of
accessibility: SynAccess option *
kind: SynBindingKind *
mustInline: bool *
isMutable: bool *
attributes: SynAttributes *
xmlDoc: PreXmlDoc *
valData: SynValData *
returnInfo: SynBindingReturnInfo option *
expr: SynExpr *
range: range *
seqPoint: DebugPointAtBinding


When documentation comments are added, use an empty line before each /// comment.

// ✔️ OK

/// 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 using the Literal attribute should place the attribute on its own line and use PascalCase naming:

// ✔️ OK

[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__

[<Literal>]
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.

// ✔️ OK - A is a top-level module.
module A

let function1 a b = a - b * b

// ✔️ OK - 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's an example using do in a class:

// ✔️ OK
type Foo () =
let foo =
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog

do
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog

// ❌ Not OK - 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's an example with do! using two spaces of indentation (because with do! there is coincidentally no difference between the approaches when using four spaces of indentation):

// ✔️ OK
async {
let! foo =
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog

do!
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
}

// ❌ Not OK - notice the "do!" expression is indented two spaces more than the let! expression
async {
let! foo =
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
do! fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
}


### Formatting computation expression operations

When creating custom operations for computation expressions, it is recommended to use camelCase naming:

// ✔️ OK
type MathBuilder () =
member _.Yield _ = 0

state + 1

[<CustomOperation("subtractOne")>]
member _.SubtractOne (state: int) =
state - 1

[<CustomOperation("divideBy")>]
member _.DivideBy (state: int, divisor: int) =
state / divisor

[<CustomOperation("multiplyBy")>]
member _.MultiplyBy (state: int, factor: int) =
state * factor

let math = MathBuilder()

let myNumber =
math {
subtractOne
divideBy 2
multiplyBy 10
}


The domain that's being modeled should ultimately drive the naming convention. If it's 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<T>), with some specific exceptions

F# allows both postfix style of writing generic types (for example, int list) and the prefix style (for example, list<int>). 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<int>.
2. For F# Options, use the postfix form: int option rather than option<int>.
3. For F# Value Options, use the postfix form: int voption rather than voption<int>.
4. For F# arrays, use the syntactic name int[] rather than int array or array<int>.
5. For Reference Cells, use int ref rather than ref<int> or Ref<int>.

For all other types, use the prefix form.

### Formatting function types

When defining the signature of a function, use white space around the -> symbol:

// ✔️ OK
type MyFun = int -> int -> string

// ❌ Not OK


### Formatting value and argument type annotations

When defining values or arguments with type annotations, use white space after the : symbol, but not before:

// ✔️ OK
let complexFunction (a: int) (b: int) c = a + b + c

let simpleValue: int = 0 // Type annotation for let-bound value

type C() =
member _.Property: int = 1

// ❌ Not OK
let complexFunctionPoorlyAnnotated (a :int) (b :int) (c:int) = a + b + c
let simpleValuePoorlyAnnotated1:int = 1
let simpleValuePoorlyAnnotated2 :int = 2


### Formatting return type annotations

In function or member return type annotations, use white space before and after the : symbol:

// ✔️ OK
let myFun (a: decimal) b c : decimal = a + b + c

type C() =
member _.SomeMethod(x: int) : int = 1

// ❌ Not OK
let myFunBad (a: decimal) b c:decimal = a + b + c

let anotherFunBad (arg: int): unit = ()

type C() =
member _.SomeMethodBad(x: int): int = 1


### Formatting types in signatures

When writing full function types in signatures, it's sometimes necessary to split the arguments over multiple lines. The return type is always indented.

For a tupled function, the arguments are separated by *, placed at the end of each line.

For example, consider a function with the following implementation:

let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...


In the corresponding signature file (.fsi extension) the function can be formatted as follows when multi-line formatting is required:

// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list


Likewise consider a curried function:

let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...


In the corresponding signature file, the -> are placed at the end of each line:

// ✔️ OK
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:

// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..


In the corresponding signature file, the types preceded by a tuple are indented

// ✔️ OK
val SampleMixedFunction:
arg1: string *
arg2: string ->
arg3: string *
arg4: string *
arg5: TType ->
arg6: TType *
arg7: TType ->
arg8: TType *
arg9: TType *
arg10: TType ->
TType list


The same rules apply for members in type signatures:

type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string


### Formatting explicit generic type arguments and constraints

The guidelines below apply to function definitions, member definitions, type definitions, and function applications.

Keep generic type arguments and constraints on a single line if it’s not too long:

// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body


If both generic type arguments/constraints and function parameters don’t fit, but the type parameters/constraints alone do, place the parameters on new lines:

// ✔️ OK
let f<'T1, 'T2 when 'T1 : equality and 'T2 : comparison>
param
=
// function body


If the type parameters or constraints are too long, break and align them as shown below. Keep the list of type parameters on the same line as the function, regardless of its length. For constraints, place when on the first line, and keep each constraint on a single line regardless of its length. Place > at the end of the last line. Indent the constraints by one level.

// ✔️ OK
let inline f< ^T1, ^T2
when ^T1 : (static member Foo1: unit -> ^T2)
and ^T2 : (member Foo2: unit -> int)
and ^T2 : (member Foo3: string -> ^T1 option)>
arg1
arg2
=
// function body


If the type parameters/constraints are broken up, but there are no normal function parameters, place the = on a new line regardless:

// ✔️ OK
let inline f<^T1, ^T2
when ^T1 : (static member Foo1: unit -> ^T2)
and ^T2 : (member Foo2: unit -> int)
and ^T2 : (member Foo3: string -> ^T1 option)>
=
// function body


The same rules apply for function applications:

// ✔️ OK
myObj
|> Json.serialize<
{| child: {| displayName: string; kind: string |}
newParent: {| id: string; displayName: string |}
requiresApproval: bool |}>

// ✔️ OK
Json.serialize<
{| child: {| displayName: string; kind: string |}
newParent: {| id: string; displayName: string |}
requiresApproval: bool |}>
myObj


## Formatting attributes

Attributes are placed above a construct:

// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...

// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x

// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }


They should go after any XML documentation:

// ✔️ OK

/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x


### Formatting attributes on parameters

Attributes can also be placed on parameters. In this case, place then on the same line as the parameter and before the name:

// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)


### Formatting multiple attributes

When multiple attributes are applied to a construct that's not a parameter, place each attribute on a separate line:

// ✔️ OK

[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }


When applied to a parameter, place attributes on the same line and separate them with a ; separator.

### Acknowledgments

These guidelines are based on A comprehensive guide to F# Formatting Conventions by Anh-Dung Phan.