These guidelines are based on the official F# code formatting guidelines.
- General rules for indentation
- Formatting white space
- Formatting operators in arithmetic expressions
- Surround a custom operator definition with white space
- Surround function parameter arrows with white space
- Surround function arguments with white space
- Place parameters on a new line for very long member definitions
- Type annotations
- Long function signatures
- Formatting blank lines
- Formatting comments
- Naming conventions
- Use camelCase for class-bound, expression-bound and pattern-bound values and functions
- Use camelCase for module-bound public functions
- Use camelCase for internal and private module-bound values and functions
- Use camelCase for parameters
- Use PascalCase for modules
- Use PascalCase for type declarations, members, and labels
- Use PascalCase for constructs intrinsic to .NET
- Avoid underscores in names
- Use standard F# operators
- Use prefix syntax for generics (Foo<T>) in preference to postfix syntax (T Foo)
- Formatting tuples
- Formatting discriminated union declarations
- Formatting discriminated unions
- Formatting record declarations
- Formatting records
- Formatting copy-and-update record expressions
- Formatting lists and arrays
- Formatting if expressions
- Formatting try/with expressions
- Formatting function parameter application
- Formatting attributes
- Formatting literals
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.
When indentation is required, you must use spaces, not tabs. At least one space is required.
We recommend 4 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.
F# is white space sensitive. Although most semantics from white space are covered by proper indentation, there are some other things to consider.
Always use white space around binary arithmetic expressions:
let subtractThenAdd x = x - 1 + 3
Unary -
operators should always have the value they are negating immediately follow:
// OK
let negate x = -x
// Bad
let negateBad x = - x
Adding a white-space character after the -
operator can lead to confusion for others.
In summary, it's important to always:
- Surround binary operators with white space
- Never have trailing white space after a unary operator
The binary arithmetic operator guideline is especially important. Failing to surround a binary -
operator, when combined with certain formatting choices, could lead to interpreting it as a unary -
.
Always use white space to surround an operator definition:
// OK
let ( !> ) x f = f x
// Bad
let (!>) x f = f x
For any custom operator that starts with *
and that has more than one character, you need to add a white space to the beginning of the definition to avoid a compiler ambiguity. Because of this, we recommend that you simply surround the definitions of all operators with a single white-space character.
When defining the signature of a function, use white space around the ->
symbol:
// OK
type MyFun = int -> int -> string
// Bad
type MyFunBad = int->int->string
When defining a function, use white space around each argument.
// OK
let myFun (a : decimal) b c = a + b + c
// Bad
let myFunBad (a:decimal)(b)c = a + b + c
If you have a very long member definition, place the parameters on new lines and indent them one scope.
type C () =
member __.LongMethodWithLotsOfParameters
(
aVeryLongType : AVeryLongTypeThatYouNeedToUse,
aSecondVeryLongType : AVeryLongTypeThatYouNeedToUse,
aThirdVeryLongType : AVeryLongTypeThatYouNeedToUse
)
=
// ... the body of the method follows
This also applies to constructors:
type C
(
aVeryLongType : AVeryLongTypeThatYouNeedToUse,
aSecondVeryLongType : AVeryLongTypeThatYouNeedToUse,
aThirdVeryLongType : AVeryLongTypeThatYouNeedToUse
)
=
// ... the body of the class follows
If a member declaration is too long to fit on one line, split it up into one member per line.
type Foo =
abstract Qux :
string
-> string
-> int
type Bar =
abstract Qux :
input : string
-> modifier : string
-> int
type Baz =
abstract Qux :
[<Attribute>] input : string
-> [<Attribute>] modifier : string
-> int
In the same way, format tuples over new lines if the declaration is too long to fit on one line:
type Foo =
abstract Qux :
string
* string
-> int
type Bar =
abstract Qux :
input : string
* modifier : string
-> int
type Baz =
abstract Qux :
[<Attribute>] input : string
* [<Attribute>] modifier : string
-> int
type Both =
/// int -> (string * int * string) -> string
abstract Qux :
i : int ->
a : string
* foo : int
* otherInput : string ->
string
When defining arguments with type annotations, use white space before and after the :
symbol:
// OK
let complexFunction (a : int) (b : int) c = a + b + c
// Bad
let complexFunctionBad (a :int) (b: int) (c:int) = a + b + c
In a let-bound function or value type annotation (return type in the case of a function), use white space before and after the :
symbol:
// OK
let expensiveToCompute : int = 0 // Type annotation for let-bound value
let myFun (a : decimal) b c : decimal = a + b + c // Type annotation for the return type of a function
// Bad
let expensiveToComputeBad1:int = 1
let expensiveToComputeBad2 :int = 2
let myFunBad (a: decimal) b c:decimal = a + b + c
If you have a very long function signature, place the parameters on new lines and indent them one scope. The return type and equal sign are also placed on their own line.
let longFunctionName
(aVeryLongFunctionParameter : int)
(anotherVeryLongFunctionParameter : int)
: int
=
// ... the body of the function
let anotherLongFunction
someParameter
anotherParameter
=
// ... the body of the function
- 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.
Generally prefer multiple double-slash comments over ML-style block comments.
// Prefer this style of comments when you want
// to express written ideas on multiple lines.
(*
ML-style comments are fine, but not a .NET-ism.
They are useful when needing to modify multi-line comments, though.
*)
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 addIAndJ i j = i + j
// Bad
let addIAndJ I J = I+J
// Bad
let AddIAndJ i j = i + j
Locally-bound functions in classes should also use camelCase.
type MyClass () =
let doSomething () =
let firstResult = ...
let secondResult = ...
member x.Result = doSomething ()
When a module-bound function is part of a public API, it should use camelCase:
module MyAPI =
let publicFunctionOne param1 param2 param2 = ...
let publicFunctionTwo param1 param2 param3 = ...
Use camelCase for private module-bound values, including the following:
-
Ad hoc functions in scripts
-
Values making up the internal implementation of a module or type
let emailMyBossTheLatestResults =
...
All parameters should use camelCase in accordance with .NET naming conventions.
module MyModule =
let myFunction paramOne paramTwo = ...
type MyClass() =
member this.MyMethod (paramOne, paramTwo) = ...
All modules (top-level, internal, private, nested) should use PascalCase.
module MyTopLevelModule
module Helpers =
module private SuperHelpers =
...
...
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.
type IMyInterface =
abstract Something : int
type MyClass () =
member this.MyMethod (x, y) = x + y
type MyRecord = { IntVal : int ; StringVal : string }
type SchoolPerson =
| Professor
| Student
| Advisor
| Administrator
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.
Historically, some F# libraries have used underscores in names. However, this is no longer widely accepted, partly because it clashes with .NET naming conventions.
Some exceptions includes interoperating with native components, where underscores are very common.
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.
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
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<int>
). Which style to use depends on local readability, but in particular:
- For F# Lists, use the postfix form:
int list
rather thanlist<int>
. - For F# Options, use the postfix form:
int option
rather thanoption<int>
. - For F# Value Options, use the postfix form:
int voption
rather thanvoption<int>
. - For F# arrays, use the syntactic name
int[]
orint array
rather thanarray<int>
. - For Reference Cells, use
int ref
rather thanref<int>
orRef<int>
.
A tuple instantiation should be parenthesized, and the delimiting commas within should be followed by a single space, for example: (1, 2)
, (x, y, z)
.
It is commonly accepted to omit parentheses in pattern matching of tuples:
let (x, y) = z // Destructuring
let x, y = z // OK
// OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
It is also commonly accepted to omit parentheses if the tuple is the return value of a function:
// OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
In summary, prefer parenthesized tuple instantiations, but when using tuples for pattern matching or a return value, it is considered fine to avoid parentheses.
Indent |
in type definition by 4 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
The F# spec does allow you to omit the pipe (|
) character in a single-case DU.
Nevertheless, prefer for consistency to use the pipe even when there is only one case.
// Preferred
type Foo =
| Foo of int
type Foo = | Foo of int
// Avoid
type Foo = Foo of int
Instantiated Discriminated Unions that split across multiple lines should give contained data a new scope with indentation.
Closing parenthesis should be on a new line.
let tree1 =
BinaryNode(
BinaryNode(BinaryValue 1, BinaryValue 2),
BinaryNode(BinaryValue 3, BinaryValue 4)
)
Indent {
in type definition by 4 spaces and start the field list on the next line:
type PostalAddress =
{
Address : string
City : string
Zip : string
}
And if you are declaring interface implementations or members on the record, note that with
should not be used:
// Declaring additional members on PostalAddress
type PostalAddress =
{
Address : string
City : string
Zip : string
}
member x.ZipAndCity = sprintf "%s %s" x.Zip x.City
type MyRecord =
{
SomeField : int
}
interface IMyInterface
Short records can be written in one line:
let point = { X = 1.0 ; Y = 0.0 }
Records that are longer should use new lines for labels:
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.
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.
Insert a space before and after semicolons:
let good = { X = 1.0 ; Y = 0.0 }
let bad = { X = 1.0; Y = 0.0 }
A copy-and-update record expression is still a record, so similar guidelines apply.
Short expressions can fit on one line:
let point2 = { point with X = 1 ; Y = 2 }
Longer expressions should use new lines:
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.
type S = { F1 : int ; F2 : string }
type State = { F : S option }
let state = { F = Some { F1 = 1 ; F2 = "Hello" } }
let newState =
{ state with
F =
Some
{
F1 = 0
F2 = ""
}
}
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:
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 {
.
// 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 } ]
// Better
[
{ 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 }]
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:
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, do...yield
may aid in readability. These cases, though subjective, should be taken into consideration.
Indentation of conditionals depends on the sizes of the expressions that make them up. If cond
, e1
and e2
are short, simply write them on one line:
if cond then e1 else e2
If any of the expressions are longer:
if cond then
e1
else
e2
For nested conditionals, else if
may appear either on a new line or on the old line:
if cond then
e1
elif cond2 then // Prefer `elif` to `else if`
e2
else
if foo then 3 // also OK
else
someMoreStuff
if cond then
e1
else if cond2 then // OK, but this could have been `elif`
e2
else
if foo then 3 // also OK
else
someMoreStuff
Where it would make code more readable, the final block of an else
or a match
may be unindented.
This is to allow flows which perform several checks in sequence, earlying out with an Error
result if any check fails, without the code marching off to the right.
if cond then
e1
elif cond2 then
e2
else
if foo then 3 // note the acceptable unindentation of this line
else
someMoreStuff // this line could be unindented too
Multiple conditionals with elif
and else
are indented at the same scope as the if
:
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
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
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
/|
.
match lam with
| Var v -> 1
| Abs (x, body) ->
1 + sizeLambda body
| App (lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Pattern matching of anonymous functions, starting by function
, should generally not indent too far. For example, indenting one scope as follows is fine:
lambdaList
|> List.map (function
| Abs (x, body) -> 1 + sizeLambda 0 body
| App (lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1
)
Note that the closing bracket has appeared on a new line, indented to the scope of the pipe.
Pattern matching in functions defined by let
or let rec
should be indented 4 spaces after starting of let
, even if function
keyword is used:
let rec sizeLambda acc = function
| Abs (x, body) -> sizeLambda (succ acc) body
| App (lam1, lam2) -> sizeLambda (sizeLambda acc lam1) lam2
| Var v -> succ acc
We do not recommend aligning arrows.
Pattern matching on the exception type should be indented at the same level as with
.
try
if System.DateTime.Now.Second % 3 = 0 then
raise (System.Exception ())
else
raise (System.ApplicationException ())
with
| :? System.ApplicationException ->
printfn "A second that was not a multiple of 3"
| _ ->
printfn "A second that was a multiple of 3"
In general, most function parameter application is done on the same line.
If you wish to apply parameters to a function on a new line, indent them by one scope.
// Best
sprintf
"\t%s - %i\n\r"
x.IngredientName
x.Quantity
// OK
sprintf "\t%s - %i\n\r"
x.IngredientName x.Quantity
// OK
sprintf
"\t%s - %i\n\r"
x.IngredientName x.Quantity
// OK
let printVolumes x =
printf "Volume in liters = %f, in us pints = %f, in imperial = %f"
(convertVolumeToLiter x)
(convertVolumeUSPint x)
(convertVolumeImperialPint x)
The same guidelines apply for lambda expressions as function arguments. If the body of a lambda expression, the body can have another line, indented by one scope.
let printListWithOffset a list1 =
List.iter
(fun elem -> printfn "%d" (a + elem))
list1
// OK if lambda body is long enough to require splitting lines
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn "%d" (a + elem)
)
list1
// OK
let printListWithOffset a list1 =
list1
|> List.iter (fun elem ->
printfn "%d" (a + elem)
)
// OK if lambda body is long enough to require splitting...
let printListWithOffset a list1 =
list1
|> List.iter (
((+) veryVeryVeryVeryLongThing)
>> printfn "%d"
)
// ... but if lambda body will fit on one line, don't split
let printListWithOffset' a list1 =
list1
|> List.iter (((+) a) >> printfn "%d")
// If any argument will not fit on a line, split all the arguments onto different lines
let mySuperFunction v =
someOtherFunction
(fun a ->
let meh = "foo"
a
)
somethingElse
(fun b -> 42)
v
However, if the body of a lambda expression is more than one line, consider factoring it out into a separate function rather than have a multi-line construct applied as a single argument to a function.
The following two functions are both correctly formatted (the function
is aligned analogously to the match
).
myList
|> List.map (
function
| Abs (x, body) -> 1 + sizeLambda 0 body
| App (lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1
)
myList
|> List.map (fun i ->
match i with
| Abs (x, body) -> 1 + sizeLambda 0 body
| App (lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1
)
Separate operators by spaces. Obvious exceptions to this rule are the !
and .
operators.
Infix expressions are OK to lineup on same column:
acc +
(sprintf "\t%s - %i\n\r"
x.IngredientName x.Quantity)
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Pipeline |>
operators should go underneath the expressions they operate on.
// 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
Code in a module must be indented relative to the module. Namespace elements do not have to be indented.
// A is a top-level module.
module A
let function1 a b = a - b * b
// 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
Object expressions and interfaces should be aligned in the same way with member
being indented after 4 spaces.
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)
}
Avoid extraneous white space in F# expressions. Also avoid extraneous brackets.
// OK
spam ham.[1]
// OK
spam (ham.[1], 3)
// Not OK
spam ( ham.[ 1 ] )
Named arguments should also not have space surrounding the =
:
// OK
let makeStreamReader x = new System.IO.StreamReader (path=x)
// Not OK
let makeStreamReader x = new System.IO.StreamReader(path = x)
Attributes are placed above a construct:
[<SomeAttribute>]
type MyClass () = ...
[<RequireQualifiedAccess>]
module M =
let f x = x
[<Struct>]
type MyRecord =
{
Label1 : int
Label2 : string
}
Attributes can also be places on parameters. In this case, place then on the same line as the parameter and before the name:
// Defines a class that takes an optional value as input defaulting to false.
type C () =
member __.M([<Optional; DefaultParameterValue(false)>] doSomething : bool)
When multiple attributes are applied to a construct that is not a parameter, they should be placed such that there is one attribute per line:
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{
Label1 : int
Label2 : string
}
When applied to a parameter, they must be on the same line and separated by a ;
separator.
F# literals using the Literal
attribute should place the attribute on its own line and use PascalCase naming:
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Avoid placing the attribute on the same line as the value.