This topic summarizes code indentation guidelines for F#. Because the F# language is sensitive to line breaks and indentation, it is not just a readability issue, aesthetic issue, or coding standardization issue to format your code correctly. You must format your code correctly for it to compile correctly.
General Rules for Indentation
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; three or four spaces of indentation at each level where indentation occurs is typical. You can configure Visual Studio to match your organization's indentation standards by changing the options in the
Options dialog box, which is available from the
Tools menu. In the
Text Editor node, expand
F# and then click
Tabs. For a description of the available options, see Options, Text Editor, All Languages, Tabs.
In general, when the compiler parses your code, it maintains an internal stack that indicates the current level of nesting. When code is indented, a new level of nesting is created, or pushed onto this internal stack. When a construct ends, the level is popped. Indentation is one way to signal the end of a level and pop the internal stack, but certain tokens also cause the level to be popped, such as the
end keyword, or a closing brace or parenthesis.
Code in a multiline construct, such as a type definition, function definition,
try...with construct, and looping constructs, must be indented relative to the opening line of the construct. The first indented line establishes a column position for subsequent code in the same construct. The indentation level is called a context. The column position sets a minimum column, referred to as an offside line, for subsequent lines of code that are in the same context. When a line of code is encountered that is indented less than this established column position, the compiler assumes that the context has ended and that you are now coding at the next level up, in the previous context. The term offside is used to describe the condition in which a line of code triggers the end of a construct because it is not indented far enough. In other words, code to the left of an offside line is offside. In correctly indented code, you take advantage of the offside rule in order to delineate the end of constructs. If you use indentation improperly, an offside condition can cause the compiler to issue a warning or can lead to the incorrect interpretation of your code.
Offside lines are determined as follows.
=token associated with a
letintroduces an offside line at the column of the first token after the
if...then...elseexpression, the column position of the first token after the
thenkeyword or the
elsekeyword introduces an offside line.
try...withexpression, the first token after
tryintroduces an offside line.
matchexpression, the first token after
withand the first token after each
->introduce offside lines.
The first token after
within a type extension introduces an offside line.
The first token after an opening brace or parenthesis, or after the
beginkeyword, introduces an offside line.
The first character in the keywords
moduleintroduce offside lines.
The following code examples illustrate the indentation rules. Here, the print statements rely on indentation to associate them with the appropriate context. Every time the indentation shifts, the context is popped and returns to the previous context. Therefore, a space is printed at the end of each iteration; "Done!" is only printed one time because the offside indentation establishes that it is not part of the loop. The printing of the string "Top-level context" is not part of the function. Therefore, it is printed first, during the static initialization, before the function is called.
let printList list1 = for elem in list1 do if elem > 0 then printf "%d" elem elif elem = 0 then printf "Zero" else printf "(Negative number)" printf " " printfn "Done!" printfn "Top-level context." printList [-1;0;1;2;3]
The output is as follows.
Top-level context (Negative number) Zero 1 2 3 Done!
When you break long lines, the continuation of the line must be indented farther than the enclosing construct. For example, function arguments must be indented farther than the first character of the function name, as shown in the following code.
let myFunction1 a b = a + b let myFunction2(a, b) = a + b let someFunction param1 param2 = let result = myFunction1 param1 param2 result * 100 let someOtherFunction param1 param2 = let result = myFunction2(param1, param2) result * 100
There are exceptions to these rules, as described in the next section.
Indentation in Modules
Code in a local module must be indented relative to the module, but code in a top-level module does not have to be indented. Namespace elements do not have to be indented.
The following code examples illustrate this.
// Program1.fs // A is a top-level module. module A let function1 a b = a - b * b
// Program2.fs // 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
For more information, see Modules.
Exceptions to the Basic Indentation Rules
The general rule, as described in the previous section, is that code in multiline constructs must be indented relative to the indentation of the first line of the construct, and that the end of the construct is determined by when the first offside line occurs. An exception to the rule about when contexts end is that some constructs, such as the
try...with expression, the
if...then...else expression, and the use of
and syntax for declaring mutually recursive functions or types, have multiple parts. You indent the later parts, such as
else in an
if...then...else expression, at the same level as the token that starts the expression, but instead of indicating an end to the context, it represents the next part of the same context. Therefore, an
if...then...else expression can be written as in the following code example.
let abs1 x = if (x >= 0) then x else -x
The exception to the offside rule applies only to the
else keywords. Therefore, although it is not an error to indent the
else further, failing to indent the lines of code in a
then block produces a warning. This is illustrated in the following lines of code.
// The following code does not produce a warning. let abs2 x = if (x >= 0) then x else -x
// The following code is not indented properly and produces a warning. let abs3 x = if (x >= 0) then x else -x
For code in an
else block, an additional special rule applies. The warning in the previous example occurs only on the code in the
then block, not on the code in the
else block. This allows you to write code that checks for various conditions at the beginning of a function without forcing the rest of the code for the function, which might be in an
else block, to be indented. Thus, you can write the following without producing a warning.
let abs4 x = if (x >= 0) then x else -x
Another exception to the rule that contexts end when a line is not indented as far as a previous line is for infix operators, such as
|>. Lines that start with infix operators are permitted to begin
(1 + oplength) columns before the normal position without triggering an end to the context, where
oplength is the number of characters that make up the operator. This causes the first token after the operator to align with the previous line.
For example, in the following code, the
+ symbol is permitted to be indented two columns less than the previous line.
let function1 arg1 arg2 arg3 arg4 = arg1 + arg2 + arg3 + arg4
Although indentation usually increases as the level of nesting becomes higher, there are several constructs in which the compiler allows you to reset the indentation to a lower column position.
The constructs that permit a reset of column position are as follows:
Bodies of anonymous functions. In the following code, the print expression starts at a column position that is farther to the left than the
funkeyword. However, the line must not start at a column to the left of the start of the previous indentation level (that is, to the left of the
let printListWithOffset a list1 = List.iter (fun elem -> printfn "%d" (a + elem)) list1
Constructs enclosed by parentheses or by
elseblock of an
if...then...elseexpression, provided the indentation is no less than the column position of the
ifkeyword. This exception allows for a coding style in which an opening parenthesis or
beginis used at the end of a line after
Bodies of modules, classes, interfaces, and structures delimited by
interface...end. This allows for a style in which the opening keyword of a type definition can be on the same line as the type name without forcing the whole body to be indented farther than the opening keyword.
type IMyInterface = interface abstract Function1: int -> int end