Riktlinjer för F#-kodformatering

Den här artikeln innehåller riktlinjer för hur du formaterar koden så att din F#-kod är:

  • Mer läsbar
  • I enlighet med konventioner som tillämpas av formateringsverktyg i Visual Studio Code och andra redigerare
  • Liknar annan kod online

Se även kodningskonventioner och riktlinjer för komponentdesign, som även omfattar namngivningskonventioner.

Automatisk kodformatering

Fantomas-kodformateraren är standardverktyget för F#-communityn för automatisk kodformatering. Standardinställningarna motsvarar den här formatguiden.

Vi rekommenderar starkt att du använder den här kodformaterare. I F#-team ska specifikationer för kodformatering överenskommas och kodas i form av en överenskommen inställningsfil för kodformateraren som checkas in på teamlagringsplatsen.

Allmänna regler för formatering

F# använder betydande blanksteg som standard och är blankstegskänsligt. Följande riktlinjer är avsedda att ge vägledning om hur du kan jonglera vissa utmaningar som detta kan medföra.

Använd blanksteg, inte flikar

När indrag krävs måste du använda blanksteg, inte flikar. F#-kod använder inte flikar, och kompilatorn ger ett fel om ett fliktecken påträffas utanför en strängliteral eller kommentar.

Använd konsekvent indrag

Vid indrag krävs minst ett utrymme. Din organisation kan skapa kodningsstandarder för att ange hur många blanksteg som ska användas för indrag. två, tre eller fyra indragsutrymmen på varje nivå där indrag inträffar är typiskt.

Vi rekommenderar fyra blanksteg per indrag.

Med detta sagt är indrag av program en subjektiv fråga. Variationer är OK, men den första regeln som du bör följa är konsekvens för indrag. Välj ett allmänt godkänt indragsformat och använd det systematiskt i hela kodbasen.

Undvik formatering som är känslig för namnlängd

Försök att undvika indrag och justering som är känslig för namngivning:

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

De främsta orsakerna till att undvika detta är:

  • Viktig kod flyttas långt till höger
  • Det finns mindre bredd kvar för den faktiska koden
  • Om du byter namn kan justeringen brytas

Undvik överflödigt tomt utrymme

Undvik överflödigt tomt utrymme i F#-kod, förutom där beskrivs i den här formatguiden.

// ✔️ OK
spam (ham 1)

// ❌ Not OK
spam ( ham 1 )

Formatera kommentarer

Föredrar flera kommentarer med dubbla snedstreck framför blockkommentar.

// 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.
*)

Kommentarer bör versalisera den första bokstaven och vara välformulerad fraser eller meningar.

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

// ❌ two poor comments
let f x = x + 1 // plus one

Information om hur du formaterar XML-dokumentkommenterar finns i "Formateringsdeklarationer" nedan.

Formateringsuttryck

I det här avsnittet beskrivs formateringsuttryck av olika slag.

Formatera stränguttryck

Strängliteraler och interpolerade strängar kan bara lämnas på en enda rad, oavsett hur lång linjen är.

let serviceStorageConnection =
    $"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"

Interpolerade uttryck med flera linjer rekommenderas inte. Binda i stället uttrycksresultatet till ett värde och använd det i den interpolerade strängen.

Formatera tuppelns uttryck

En tuppel-instansiering ska parenteseras och avgränsande kommatecken i den ska följas av ett enda blanksteg, till exempel: (1, 2), (x, y, z).

// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]

Det är allmänt accepterat att utelämna parenteser i mönstermatchning av tupplar:

// ✔️ OK
let (x, y) = z
let x, y = z

// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1

Det är också allmänt accepterat att utelämna parenteser om tuppeln är returvärdet för en funktion:

// ✔️ OK
let update model msg =
    match msg with
    | 1 -> model + 1, []
    | _ -> model, [ msg ]

Sammanfattningsvis föredrar du parentesiserade tuppelns instansier, men när du använder tupplar för mönstermatchning eller ett returvärde anses det bra att undvika parenteser.

Formatera programuttryck

När du formaterar en funktion eller ett metodprogram anges argument på samma rad när radbredd tillåter:

// ✔️ OK
someFunction1 x.IngredientName x.Quantity

Utelämna parenteser om inte argumenten kräver dem:

// ✔️ OK
someFunction1 x.IngredientName

// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)

// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)

Utelämna inte blanksteg när du anropar med flera curryargument:

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

I standardformateringskonventioner läggs ett blanksteg till när gemener tillämpas på tupled eller parentesiserade argument (även när ett enda argument används):

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

I standardformateringskonventioner läggs inget utrymme till när versaler tillämpas på tupled-argument. Detta beror på att dessa ofta används med flytande programmering:

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

Du kan behöva skicka argument till en funktion på en ny rad som en fråga om läsbarhet eller eftersom listan med argument eller argumentnamnen är för lång. I så fall kan du dra in en nivå:

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

När funktionen tar ett enda Tupled-argument med flera rader placerar du varje argument på en ny rad:

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

Om argumentuttrycken är korta, avgränsar du argumenten med blanksteg och behåller dem på en rad.

// ✔️ OK
let person = new Person(a1, a2)

// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)

// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)

Om argumentuttrycken är långa använder du nya rader och drar in en nivå i stället för att dra in till vänsterparentesen.

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

Samma regler gäller även om det bara finns ett enda argument med flera rader, inklusive flerradssträngar:

// ✔️ 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" ]
)

Formatera pipelineuttryck

När du använder flera rader bör pipelineoperatorer |> gå under de uttryck som de använder.

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

Formatera lambda-uttryck

När ett lambda-uttryck används som ett argument i ett flerradsuttryck och följs av andra argument placerar du brödtexten för ett lambda-uttryck på en ny rad, indragen med en nivå:

// ✔️ OK
let printListWithOffset a list1 =
    List.iter
        (fun elem ->
             printfn $"A very long line to format the value: %d{a + elem}")
        list1

Om lambda-argumentet är det sista argumentet i ett funktionsprogram placerar du alla argument tills pilen på samma rad.

// ✔️ 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}")

Behandla match lambda's på ett liknande sätt.

// ✔️ OK
functionName arg1 arg2 arg3 (function
    | Choice1of2 x -> 1
    | Choice2of2 y -> 2)

När det finns många inledande eller flerradsargument innan lambda drar in alla argument med en nivå.

// ✔️ OK
functionName
    arg1
    arg2
    arg3
    (fun arg4 ->
        bodyExpr)

// ✔️ OK
functionName
    arg1
    arg2
    arg3
    (function
     | Choice1of2 x -> 1
     | Choice2of2 y -> 2)

Om brödtexten i ett lambda-uttryck är flera rader långt bör du överväga att omstrukturera det till en lokalt begränsad funktion.

När pipelines innehåller lambda-uttryck är varje lambda-uttryck vanligtvis det sista argumentet i varje steg i pipelinen:

// ✔️ 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}")

Om argumenten för en lambda inte får plats på en enda rad, eller själva är flera rader, placerar du dem på nästa rad, indragen med en nivå.

// ✔️ OK
fun
    (aVeryLongParameterName: AnEquallyLongTypeName)
    (anotherVeryLongParameterName: AnotherLongTypeName)
    (yetAnotherLongParameterName: LongTypeNameAsWell)
    (youGetTheIdeaByNow: WithLongTypeNameIncluded) ->
    // code starts here
    ()

// ❌ Not OK, code formatters will reformat to the above to respect the maximum line length.
fun (aVeryLongParameterName: AnEquallyLongTypeName) (anotherVeryLongParameterName: AnotherLongTypeName) (yetAnotherLongParameterName: LongTypeNameAsWell) (youGetTheIdeaByNow: WithLongTypeNameIncluded) ->
    ()

// ✔️ OK
let useAddEntry () =
    fun
        (input:
            {| name: string
               amount: Amount
               isIncome: bool
               created: string |}) ->
         // foo
         bar ()

// ❌ Not OK, code formatters will reformat to the above to avoid reliance on whitespace alignment that is contingent to length of an identifier.
let useAddEntry () =
    fun (input: {| name: string
                   amount: Amount
                   isIncome: bool
                   created: string |}) ->
        // foo
        bar ()

Formatera aritmetiska och binära uttryck

Använd alltid tomt utrymme runt binära aritmetiska uttryck:

// ✔️ OK
let subtractThenAdd x = x - 1 + 3

Om du inte omger en binär - operator i kombination med vissa formateringsalternativ kan det leda till att den tolkas som en unary -. Unary-operatorer - bör alltid omedelbart följas av värdet de negerar:

// ✔️ OK
let negate x = -x

// ❌ Not OK
let negateBad x = - x

Att lägga till ett blankstegstecken efter operatorn - kan leda till förvirring för andra.

Avgränsa binära operatorer med blanksteg. Infixuttryck är OK för att uppställning i samma kolumn:

// ✔️ OK
let function1 () =
    acc +
    (someFunction
         x.IngredientName x.Quantity)

// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
    arg1 + arg2 +
    arg3 + arg4

Den här regeln gäller även för måttenheter i typer och konstanta anteckningar:

// ✔️ 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)> }

Följande operatorer definieras i F#-standardbiblioteket och bör användas i stället för att definiera motsvarigheter. Att använda dessa operatorer rekommenderas eftersom det tenderar att göra koden mer läsbar och idiomatisk. I följande lista sammanfattas de rekommenderade F#-operatorerna.

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

Formateringsintervalloperatoruttryck

Lägg bara till blanksteg runt .. när alla uttryck inte är atomiska. Heltal och enstaka ordidentifierare betraktas som atomiska.

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

Dessa regler gäller även för segmentering:

// ✔️ OK
arr[0..10]
list[..^1]

Formatering om uttryck

Indrag av villkor beror på storleken och komplexiteten för de uttryck som utgör dem. Skriv dem på en rad när:

  • cond, e1och e2 är korta.
  • e1 och e2 är inte if/then/else själva uttrycken.
// ✔️ OK
if cond then e1 else e2

Om else-uttrycket saknas rekommenderar vi att du aldrig skriver hela uttrycket på en rad. Detta är för att skilja den imperativa koden från funktionen.

// ✔️ OK
if a then
    ()

// ❌ Not OK, code formatters will reformat to the above by default
if a then ()

Om något av uttrycken är flera rader bör varje villkorsgren vara flera rader.

// ✔️ OK
if cond then
    let e1 = something()
    e1
else
    e2
    
// ❌ Not OK
if cond then
    let e1 = something()
    e1
else e2

Flera villkor med elif och är indragna i samma omfång som if när de följer reglerna för enradsuttrycken if/then/elseelse.

// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4

Om något av villkoren eller uttrycken är flera rader är hela if/then/else uttrycket flera rader:

// ✔️ OK
if cond1 then
    let e1 = something()
    e1
elif cond2 then
    e2
elif cond3 then
    e3
else
    e4

// ❌ Not OK
if cond1 then
    let e1 = something()
    e1
elif cond2 then e2
elif cond3 then e3
else e4

Om ett villkor är flera rader eller överskrider standardtoleransen för en rad, bör villkorsuttrycket använda en indrag och en ny rad. Nyckelordet if och then bör justeras när det långa villkorsuttrycket kapslars in.

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

Det är dock bättre format att omstrukturera långa villkor till en let-bindning eller separat funktion:

// ✔️ OK
let performAction =
    complexExpression a b && env.IsDevelopment()
    || someFunctionToCall
        aVeryLongParameterNameOne
        aVeryLongParameterNameTwo
        aVeryLongParameterNameThree

if performAction then
    e1
else
    e2

Formatera unionsfalluttryck

Tillämpning av fall av diskriminerade fackföreningar följer samma regler som funktions- och metodprogram. Eftersom namnet är versalt tar kodformatanter bort ett blanksteg före en tuppeln:

// ✔️ OK
let opt = Some("A", 1)

// OK, but code formatters will remove the space
let opt = Some ("A", 1)

Precis som funktionsprogram bör konstruktioner som delas upp över flera linjer använda indrag:

// ✔️ OK
let tree1 =
    BinaryNode(
        BinaryNode (BinaryValue 1, BinaryValue 2),
        BinaryNode (BinaryValue 3, BinaryValue 4)
    )

Formateringslista och matrisuttryck

Skriv x :: l med blanksteg runt operatorn :: (:: är en infixoperator, därför omgiven av blanksteg).

Lista och matriser som deklareras på en enda rad bör ha ett blanksteg efter den inledande hakparentesen och före den avslutande hakparentesen:

// ✔️ OK
let xs = [ 1; 2; 3 ]

// ✔️ OK
let ys = [| 1; 2; 3; |]

Använd alltid minst ett blanksteg mellan två distinkta klammerparentesliknande operatorer. Lämna till exempel ett blanksteg mellan en [ och en {.

// ✔️ 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 }]

Samma riktlinje gäller för listor eller matriser med tupplar.

Listor och matriser som delas över flera rader följer en liknande regel som poster gör:

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

Precis som med poster blir det enklare att deklarera inledande och avslutande hakparenteser på sin egen rad, vilket gör det enklare att flytta runt koden och skicka den till funktioner:

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

Om en lista eller ett matrisuttryck är den högra sidan av en bindning kanske du föredrar att använda Stroustrup formatmall:

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

Men om ett list- eller matrisuttryck inte är höger sida av en bindning, till exempel när det finns i en annan lista eller matris, bör hakparenteserna gå på sina egna linjer om det inre uttrycket behöver sträcka sig över flera rader:

// ✔️ OK - The outer list follows `Stroustrup` style, while the inner lists place their brackets on separate lines
let fn a b = [ 
    [
        someReallyLongValueThatWouldForceThisListToSpanMultipleLines
        a
    ]
    [ 
        b
        someReallyLongValueThatWouldForceThisListToSpanMultipleLines 
    ]
]

// ❌ Not okay
let fn a b = [ [
    someReallyLongValueThatWouldForceThisListToSpanMultipleLines
    a
]; [
    b
    someReallyLongValueThatWouldForceThisListToSpanMultipleLines
] ]

Samma regel gäller för posttyper i matriser/listor:

// ✔️ OK - The outer list follows `Stroustrup` style, while the inner lists place their brackets on separate lines
let fn a b = [ 
    {
        Foo = someReallyLongValueThatWouldForceThisListToSpanMultipleLines
        Bar = a
    }
    { 
        Foo = b
        Bar = someReallyLongValueThatWouldForceThisListToSpanMultipleLines 
    }
]

// ❌ Not okay
let fn a b = [ {
    Foo = someReallyLongValueThatWouldForceThisListToSpanMultipleLines
    Bar = a
}; {
    Foo = b
    Bar = someReallyLongValueThatWouldForceThisListToSpanMultipleLines
} ]

När du genererar matriser och listor programmatiskt föredrar du -> framför do ... yield när ett värde alltid genereras:

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

Äldre versioner av F# krävs för att yield ange i situationer där data kan genereras villkorligt, eller så kan det finnas på varandra följande uttryck som ska utvärderas. Föredrar att utelämna dessa yield nyckelord om du inte måste kompilera med en äldre F#-språkversion:

// ✔️ 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"
    ]

I vissa fall do...yield kan underlätta läsbarheten. Dessa fall, även om de är subjektiva, bör beaktas.

Formatera postuttryck

Korta poster kan skrivas på en rad:

// ✔️ OK
let point = { X = 1.0; Y = 0.0 }

Poster som är längre bör använda nya rader för etiketter:

// ✔️ OK
let rainbow =
    { Boss = "Jeffrey"
      Lackeys = ["Zippy"; "George"; "Bungle"] }

Formatmallar för flerradsparentes

För poster som sträcker sig över flera rader finns det tre formatmallar som ofta används: Cramped, Alignedoch Stroustrup. Formatet Cramped har varit standardformatet för F#-kod, eftersom det tenderar att gynna formatmallar som gör det möjligt för kompilatorn att enkelt parsa kod. Både Aligned format och Stroustrup format möjliggör enklare omordning av medlemmar, vilket leder till kod som kan vara lättare att omstrukturera, med nackdelen att vissa situationer kan kräva lite mer utförlig kod.

  • Cramped: Den historiska standarden och standardformatet för F#-post. Inledande hakparenteser går på samma rad som den första medlemmen och stänger hakparentesen på samma rad som den sista medlemmen.

    let rainbow = 
        { Boss1 = "Jeffrey"
          Boss2 = "Jeffrey"
          Boss3 = "Jeffrey"
          Lackeys = [ "Zippy"; "George"; "Bungle" ] }
    
  • Aligned: Hakparenteser får varsin rad, justerad på samma kolumn.

    let rainbow =
        {
            Boss1 = "Jeffrey"
            Boss2 = "Jeffrey"
            Boss3 = "Jeffrey"
            Lackeys = ["Zippy"; "George"; "Bungle"]
        }
    
  • Stroustrup: Inledande hakparentes går på samma linje som bindningen, avslutande hakparentes får sin egen linje.

    let rainbow = {
        Boss1 = "Jeffrey"
        Boss2 = "Jeffrey"
        Boss3 = "Jeffrey"
        Lackeys = [ "Zippy"; "George"; "Bungle" ]
    }
    

Samma formateringsformatregler gäller för list- och matriselement.

Formatera postuttryck för kopiering och uppdatering

Ett postuttryck för kopiering och uppdatering är fortfarande en post, så liknande riktlinjer gäller.

Korta uttryck får plats på en rad:

// ✔️ OK
let point2 = { point with X = 1; Y = 2 }

Längre uttryck bör använda nya rader och format baserat på någon av de ovan namngivna konventionerna:

// ✔️ OK - Cramped
let newState =
    { state with
        Foo =
            Some
                { F1 = 0
                  F2 = "" } }
        
// ✔️ OK - Aligned
let newState = 
    {
        state with
            Foo =
                Some
                    {
                        F1 = 0
                        F2 = ""
                    }
    }

// ✔️ OK - Stroustrup
let newState = { 
    state with
        Foo =
            Some { 
                F1 = 0
                F2 = ""
            }
}

Obs! Om du använder Stroustrup formatmall för kopierings- och uppdateringsuttryck måste du dra in medlemmar längre än det kopierade postnamnet:

// ✔️ OK
let bilbo = {
    hobbit with 
        Name = "Bilbo"
        Age = 111
        Region = "The Shire" 
}

// ❌ Not OK - Results in compiler error: "Possible incorrect indentation: this token is offside of context started at position"
let bilbo = {
    hobbit with 
    Name = "Bilbo"
    Age = 111
    Region = "The Shire" 
}

Formateringsmönstermatchning

Använd en | för varje sats i en matchning utan indrag. Om uttrycket är kort kan du överväga att använda en enda rad om varje underuttryck också är enkelt.

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

Om uttrycket till höger om mönstermatchningspilen är för stort flyttar du det till följande rad, indraget ett steg från match/|.

// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
    1 + sizeLambda body
| App(lam1, lam2) ->
    sizeLambda lam1 + sizeLambda lam2

Om ett matchningsuttryck är flera rader eller överskrider standardtoleransen för en rad, bör matchningsuttrycket använda en indrag och en ny rad. Nyckelordet match och with bör justeras när det långa matchningsuttrycket kapslar in.

// ✔️ OK, but better to refactor, see below
match
    complexExpression a b && env.IsDevelopment()
    || someFunctionToCall
        aVeryLongParameterNameOne
        aVeryLongParameterNameTwo
        aVeryLongParameterNameThree 
with
| X y -> y
| _ -> 0

Det är dock bättre format att omstrukturera långa matchningsuttryck till en let-bindning eller separat funktion:

// ✔️ OK
let performAction =
    complexExpression a b && env.IsDevelopment()
    || someFunctionToCall
        aVeryLongParameterNameOne
        aVeryLongParameterNameTwo
        aVeryLongParameterNameThree

match performAction with
| X y -> y
| _ -> 0

Du bör undvika att justera pilarna i en mönstermatchning.

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

Mönstermatchning som introduceras med hjälp av nyckelordet function bör dra in en nivå från början av föregående rad:

// ✔️ OK
lambdaList
|> List.map (function
    | Abs(x, body) -> 1 + sizeLambda 0 body
    | App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
    | Var v -> 1)

Användning av function i funktioner som definieras av let eller let rec bör i allmänhet undvikas till förmån för en match. Om det används bör mönsterreglerna överensstämma med nyckelordet 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

Formatera try/with expressions

Mönstermatchning för undantagstypen ska vara indraget på samma nivå som 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"

Lägg till en | för varje sats, förutom när det bara finns en enda sats:

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

Formatering med namn på argument

Namngivna argument bör ha blanksteg som =omger :

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

När mönstermatchning med hjälp av diskriminerade fackföreningar formateras namngivna mönster på liknande sätt, till exempel.

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

Formatera mutationsuttryck

Mutationsuttryck formateras location <- expr normalt på en rad. Om formatering med flera rader krävs placerar du uttrycket till höger på en ny rad.

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

Formatera objektuttryck

Objektuttrycksmedlemmar bör justeras med member indrag på en nivå.

// ✔️ 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) }

Du kanske också föredrar att använda Stroustrup formatmall:

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)
}

Tomma typdefinitioner kan formateras på en rad:

type AnEmptyType = class end

Oavsett den valda sidbredden = class end bör alltid finnas på samma rad.

Formatera index/segmentuttryck

Indexuttryck får inte innehålla blanksteg runt de inledande och avslutande hakparenteserna.

// ✔️ OK
let v = expr[idx]
let y = myList[0..1]

// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]

Detta gäller även för den äldre expr.[idx] syntaxen.

// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]

// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]

Formatera citerade uttryck

Avgränsarsymbolerna (<@ , @>, <@@, ) @@>ska placeras på separata rader om det citerade uttrycket är ett flerradsuttryck.

// ✔️ OK
<@
    let f x = x + 10
    f 20
@>

// ❌ Not OK
<@ let f x = x + 10
   f 20
@>

I enradsuttryck ska avgränsarsymbolerna placeras på samma rad som själva uttrycket.

// ✔️ OK
<@ 1 + 1 @>

// ❌ Not OK
<@
    1 + 1
@>

Formatera länkade uttryck

När länkade uttryck (funktionsprogram som är sammanflätade med .) är långa placerar du varje programanrop på nästa rad. Dra in efterföljande länkar i kedjan med en nivå efter den inledande länken.

// ✔️ OK
Host
    .CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())

// ✔️ OK
Cli
    .Wrap("git")
    .WithArguments(arguments)
    .WithWorkingDirectory(__SOURCE_DIRECTORY__)
    .ExecuteBufferedAsync()
    .Task

Den inledande länken kan bestå av flera länkar om de är enkla identifierare. Till exempel tillägg av ett fullständigt kvalificerat namnområde.

// ✔️ OK
Microsoft.Extensions.Hosting.Host
    .CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())

Efterföljande länkar bör också innehålla enkla identifierare.

// ✔️ OK
configuration.MinimumLevel
    .Debug()
    // Notice how `.WriteTo` does not need its own line.
    .WriteTo.Logger(fun loggerConfiguration ->
        loggerConfiguration.Enrich
            .WithProperty("host", Environment.MachineName)
            .Enrich.WithProperty("user", Environment.UserName)
            .Enrich.WithProperty("application", context.HostingEnvironment.ApplicationName))

När argumenten i ett funktionsprogram inte får plats på resten av raden placerar du varje argument på nästa rad.

// ✔️ OK
WebHostBuilder()
    .UseKestrel()
    .UseUrls("http://*:5000/")
    .UseCustomCode(
        longArgumentOne,
        longArgumentTwo,
        longArgumentThree,
        longArgumentFour
    )
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseStartup<Startup>()
    .Build()

// ✔️ OK
Cache.providedTypes
    .GetOrAdd(cacheKey, addCache)
    .Value

// ❌ Not OK, formatting tools will reformat to the above
Cache
    .providedTypes
    .GetOrAdd(
        cacheKey,
        addCache
    )
    .Value

Lambda-argument i ett funktionsprogram bör starta på samma rad som öppningen (.

// ✔️ OK
builder
    .WithEnvironment()
    .WithLogger(fun loggerConfiguration ->
        // ...
        ())

// ❌ Not OK, formatting tools will reformat to the above
builder
    .WithEnvironment()
    .WithLogger(
        fun loggerConfiguration ->
        // ...
        ())

Formateringsdeklarationer

I det här avsnittet beskrivs formateringsdeklarationer av olika slag.

Lägga till tomma rader mellan deklarationer

Avgränsa funktions- och klassdefinitioner på den översta nivån med en enda tom rad. Till exempel:

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

Om en konstruktion har XML-dokumentkommentarer lägger du till en tom rad före kommentaren.

// ✔️ OK

/// This is a function
let thisFunction() =
    1 + 1

/// This is another function, note the blank line before this line
let thisFunction() =
    1 + 1

Formatering av let- och member-deklarationer

Vid formatering let och member deklarationer går vanligtvis den högra sidan av en bindning antingen på en rad eller (om den är för lång) på en ny rad med indrag på en nivå.

Följande exempel är till exempel kompatibla:

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

Dessa är inkompatibla:

// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""

let d = while f do
    printfn "%A" x

Instansieringar av posttyp kan också placera hakparenteserna på sina egna linjer:

// ✔️ OK
let bilbo =
    { 
        Name = "Bilbo"
        Age = 111
        Region = "The Shire" 
    }

Du kanske också föredrar att använda Stroustrup formatmall, med öppningen { på samma rad som bindningsnamnet:

// ✔️ OK
let bilbo = {
    Name = "Bilbo"
    Age = 111
    Region = "The Shire"
}

Avgränsa medlemmar med en enda tom rad och dokument och lägg till en dokumentationskommentar:

// ✔️ OK

/// This is a thing
type ThisThing(value: int) =

    /// Gets the value
    member _.Value = value

    /// Returns twice the value
    member _.TwiceValue() = value*2

Extra tomma rader kan användas (sparsamt) för att avgränsa grupper med relaterade funktioner. Tomma rader kan utelämnas mellan ett gäng relaterade one-liners (till exempel en uppsättning dummy-implementeringar). Använd tomma rader i funktioner, sparsamt, för att ange logiska avsnitt.

Formateringsfunktion och medlemsargument

När du definierar en funktion använder du blanksteg runt varje 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

Om du har en lång funktionsdefinition placerar du parametrarna på nya rader och drar in dem för att matcha indragsnivån för den efterföljande parametern.

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

Detta gäller även för medlemmar, konstruktorer och parametrar som använder tupplar:

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

// ✔️ OK
type TypeWithLongSecondaryConstructor () =
    new
        (
            aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
            aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
            aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse
        ) =
        // ... the body of the constructor follows

Om parametrarna är härdade placerar du = tecknet tillsammans med en returtyp på en ny rad:

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

Det här är ett sätt att undvika för långa rader (om returtypen kan ha ett långt namn) och få mindre radskador när du lägger till parametrar.

Formateringsoperatordeklarationer

Du kan också använda tomt utrymme för att omge en operatordefinition:

// ✔️ OK
let ( !> ) x f = f x

// ✔️ OK
let (!>) x f = f x

För alla anpassade operatorer som börjar med * och som har fler än ett tecken måste du lägga till ett tomt utrymme i början av definitionen för att undvika tvetydigheter i kompilatorn. Därför rekommenderar vi att du bara omger definitionerna för alla operatorer med ett enda blankstegstecken.

Formatera postdeklarationer

För postdeklarationer bör du som standard dra in { i typdefinitionen med fyra blanksteg, starta etikettlistan på samma rad och justera eventuella medlemmar med { token:

// ✔️ OK
type PostalAddress =
    { Address: string
      City: string
      Zip: string }

Det är också vanligt att föredra att sätta hakparenteser på sin egen linje, med etiketter indragna av ytterligare fyra blanksteg:

// ✔️ OK
type PostalAddress =
    { 
        Address: string
        City: string
        Zip: string 
    }

Du kan också placera { i slutet av den första raden i typdefinitionen (Stroustrup format):

// ✔️ OK
type PostalAddress = {
    Address: string
    City: string
    Zip: string
}

Om ytterligare medlemmar behövs ska du inte använda with/end när det är möjligt:

// ✔️ OK
type PostalAddress =
    { Address: string
      City: string
      Zip: string }
    member x.ZipAndCity = $"{x.Zip} {x.City}"

// ❌ 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
  
// ✔️ OK
type PostalAddress =
    { 
        Address: string
        City: string
        Zip: string 
    }
    member x.ZipAndCity = $"{x.Zip} {x.City}"
    
// ❌ 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

Undantaget för den här formatmallsregeln är om du formaterar poster enligt Stroustrup formatet. I det här fallet krävs nyckelordet with på grund av kompilatorregler om du vill implementera ett gränssnitt eller lägga till ytterligare medlemmar:

// ✔️ OK
type PostalAddress = {
    Address: string
    City: string
    Zip: string
} with
    member x.ZipAndCity = $"{x.Zip} {x.City}"
   
// ❌ Not OK, this is currently invalid F# code
type PostalAddress = {
    Address: string
    City: string
    Zip: string
} 
member x.ZipAndCity = $"{x.Zip} {x.City}"

När XML-dokumentation läggs till för postfält, Aligned eller Stroustrup formatmall föredras, och ytterligare blanksteg ska läggas till mellan medlemmar:

// ❌ Not OK - putting { and comments on the same line should be avoided
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}"

// ✔️ 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}"

// ✔️ OK - Stroustrup Style
type PostalAddress = {
    /// The address
    Address: string

    /// The city
    City: string

    /// The zip code
    Zip: string
} with
    /// Format the zip code and the city
    member x.ZipAndCity = $"{x.Zip} {x.City}"

Att placera öppningstoken på en ny rad och den avslutande token på en ny rad är att föredra om du deklarerar gränssnittsimplementeringar eller medlemmar i posten:

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

// ✔️ OK
type MyRecord =
    {
        /// The record field
        SomeField: int
    }
    interface IMyInterface

Samma regler gäller för anonyma posttypalias.

Formatera diskriminerade fackliga deklarationer

För diskriminerade fackliga deklarationer, indrag | i typdefinition med fyra blanksteg:

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

När det finns en enda kort union kan du utelämna den ledande |.

// ✔️ OK
type Address = Address of string

För en längre eller flerradsunion ska du behålla | och placera varje unionfält på en ny rad, med avgränsaren * i slutet av varje rad.

// ✔️ OK
[<NoEquality; NoComparison>]
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

När du lägger till dokumentationskommentarer använder du en tom rad före varje /// kommentar.

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

Formatera literaldeklarationer

F#-literaler med attributet Literal ska placera attributet på sin egen rad och använda PascalCase-namngivning:

// ✔️ OK

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

[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"

Undvik att placera attributet på samma rad som värdet.

Formatering av moduldeklarationer

Kod i en lokal modul måste vara indraget i förhållande till modulen, men koden i en modul på den översta nivån ska inte vara indragen. Namnområdeselement behöver inte vara indragna.

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

Formatering gör deklarationer

I typdeklarationer, moduldeklarationer och beräkningsuttryck krävs ibland användning av do eller do! för sidoeffektåtgärder. När dessa sträcker sig över flera rader använder du indrag och en ny rad för att hålla indraget konsekvent med let/let!. Här är ett exempel som använder do i en klass:

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

Här är ett exempel på do! hur du använder två indragsutrymmen (eftersom do! det av en tillfällighet inte finns någon skillnad mellan metoderna när du använder fyra indragsutrymmen):

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

Formatera beräkningsuttrycksåtgärder

När du skapar anpassade åtgärder för beräkningsuttryck rekommenderar vi att du använder camelCase-namngivning:

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

    [<CustomOperation("addOne")>]
    member _.AddOne (state: int) =
        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 {
        addOne
        addOne
        addOne
        subtractOne
        divideBy 2
        multiplyBy 10
    }

Domänen som modelleras bör i slutändan driva namngivningskonventionen. Om det är idiomatiskt att använda en annan konvention bör den konventionen användas i stället.

Om returvärdet för ett uttryck är ett beräkningsuttryck föredrar du att placera nyckelordsnamnet för beräkningsuttrycket på sin egen rad:

// ✔️ OK
let foo () = 
    async {
        let! value = getValue()
        do! somethingElse()
        return! anotherOperation value 
    }

Du kanske också föredrar att placera beräkningsuttrycket på samma rad som bindningsnamnet:

// ✔️ OK
let foo () = async {
    let! value = getValue()
    do! somethingElse()
    return! anotherOperation value 
}

Oavsett vad du föredrar bör du sträva efter att förbli konsekvent i hela din kodbas. Formatters kan göra att du kan ange att den här inställningen ska förbli konsekvent.

Formateringstyper och skriv anteckningar

I det här avsnittet beskrivs formateringstyper och skrivanteckningar. Detta inkluderar formatering av signaturfiler med .fsi tillägget.

För typer föredrar du prefixsyntax för generiska objekt (Foo<T>), med vissa specifika undantag

F# tillåter både postfixstil för att skriva generiska typer (till exempel int list) och prefixstilen (till exempel list<int>). Postfix-formatmallen kan bara användas med ett argument av en enda typ. Föredrar alltid .NET-formatmallen, förutom fem specifika typer:

  1. För F#-listor använder du postfixformuläret: int list i stället list<int>för .
  2. För F#-alternativ använder du postfixformuläret: int option i stället option<int>för .
  3. För F#-värdealternativ använder du postfixformuläret: int voption i stället voption<int>för .
  4. För F#-matriser använder du postfixformuläret: int array i stället för array<int> eller int[].
  5. För referensceller använder du int ref i stället för ref<int> eller Ref<int>.

Använd prefixformuläret för alla andra typer.

Formateringsfunktionstyper

När du definierar signaturen för en funktion använder du blanksteg runt symbolen -> :

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

// ❌ Not OK
type MyFunBad = int->int->string

Formateringsvärde och argumenttypanteckningar

När du definierar värden eller argument med typanteckningar använder du blanksteg efter symbolen : , men inte före:

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

Formatera anteckningar av flerradstyp

När en typanteckning är lång eller flera rader placerar du dem på nästa rad, indragen med en nivå.

type ExprFolder<'State> =
    { exprIntercept: 
        ('State -> Expr -> 'State) -> ('State -> Expr -> 'State -> 'State -> Exp -> 'State }
        
let UpdateUI
    (model:
#if NETCOREAPP2_1
        ITreeModel
#else
        TreeModel
#endif
    )
    (info: FileInfo) =
    // code
    ()

let f
    (x:
        {|
            a: Second
            b: Metre
            c: Kilogram
            d: Ampere
            e: Kelvin
            f: Mole
            g: Candela
        |})
    =
    x.a

type Sample
    (
        input: 
            LongTupleItemTypeOneThing * 
            LongTupleItemTypeThingTwo * 
            LongTupleItemTypeThree * 
            LongThingFour * 
            LongThingFiveYow
    ) =
    class
    end

För interna anonyma posttyper kan du också använda Stroustrup formatmall:

let f
    (x: {|
        x: int
        y: AReallyLongTypeThatIsMuchLongerThan40Characters
     |})
    =
    x

Formatering av returtypsanteckningar

Använd blanksteg före och efter symbolen : i kommentarer för funktions- eller medlemsreturtyp:

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

Formateringstyper i signaturer

När du skriver fullständiga funktionstyper i signaturer är det ibland nödvändigt att dela argumenten över flera rader. Returtypen är alltid indragen.

För en tupled-funktion avgränsas argumenten med *, placerade i slutet av varje rad.

Tänk dig till exempel en funktion med följande implementering:

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

I motsvarande signaturfil (.fsi tillägg) kan funktionen formateras på följande sätt när formatering med flera rader krävs:

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

Överväg på samma sätt en curryfunktion:

let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...

I motsvarande signaturfil -> placeras de i slutet av varje rad:

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

På samma sätt bör du överväga en funktion som tar en blandning av curry- och tupled-argument:

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

I motsvarande signaturfil är de typer som föregås av en tuppeln indragna

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

Samma regler gäller för medlemmar i typsignaturer:

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

Formatera explicita allmänna typargument och begränsningar

Riktlinjerna nedan gäller för funktionsdefinitioner, medlemsdefinitioner, typdefinitioner och funktionsprogram.

Behåll allmänna typargument och begränsningar på en enda rad om den inte är för lång:

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

Om både allmänna typargument/begränsningar och funktionsparametrar inte passar, men enbart typparametrarna/begränsningarna gör det, placerar du parametrarna på nya rader:

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

Om typparametrarna eller begränsningarna är för långa kan du bryta och justera dem enligt nedan. Behåll listan med typparametrar på samma rad som funktionen, oavsett dess längd. För begränsningar placerar du when på den första raden och behåller varje begränsning på en enskild rad oavsett längd. Placera > i slutet av den sista raden. Dra in begränsningarna på en nivå.

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

Om typparametrarna/begränsningarna är uppdelade, men det inte finns några normala funktionsparametrar, placerar du på = en ny rad oavsett:

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

Samma regler gäller för funktionsprogram:

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

Formatera arv

Argumenten för basklasskonstruktorn visas i argumentlistan i inherit -satsen. inherit Placera satsen på en ny rad, indragen med en nivå.

type MyClassBase(x: int) =
   class
   end

// ✔️ OK
type MyClassDerived(y: int) =
   inherit MyClassBase(y * 2)

// ❌ Not OK
type MyClassDerived(y: int) = inherit MyClassBase(y * 2)

När konstruktorn är lång eller flera rader placerar du dem på nästa rad, indraget på en nivå.
Formatera den här flerradskonstruktorn enligt reglerna för program med flera funktioner.

type MyClassBase(x: string) =
   class
   end

// ✔️ OK
type MyClassDerived(y: string) =
    inherit 
        MyClassBase(
            """
            very long
            string example
            """
        )
        
// ❌ Not OK
type MyClassDerived(y: string) =
    inherit MyClassBase(
        """
        very long
        string example
        """)

Formatera den primära konstruktorn

I standardformateringskonventionerna läggs inget utrymme till mellan typnamnet och parenteserna för den primära konstruktorn.

// ✔️ OK
type MyClass() =
    class
    end

type MyClassWithParams(x: int, y: int) =
    class
    end
        
// ❌ Not OK
type MyClass () =
    class
    end

type MyClassWithParams (x: int, y: int) =
    class
    end

Flera konstruktorer

inherit När satsen är en del av en post placerar du den på samma rad om den är kort. Och placera den på nästa rad, indragen med en nivå, om den är lång eller flera rader.

type BaseClass =
    val string1: string
    new () = { string1 = "" }
    new (str) = { string1 = str }

type DerivedClass =
    inherit BaseClass

    val string2: string
    new (str1, str2) = { inherit BaseClass(str1); string2 = str2 }
    new () = 
        { inherit 
            BaseClass(
                """
                very long
                string example
                """
            )
          string2 = str2 }

Formateringsattribut

Attribut placeras ovanför en konstruktion:

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

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

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

De bör gå efter all XML-dokumentation:

// ✔️ OK

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

Formateringsattribut för parametrar

Attribut kan också placeras på parametrar. I det här fallet placerar du sedan på samma rad som parametern och före namnet:

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

Formatera flera attribut

När flera attribut tillämpas på en konstruktion som inte är en parameter placerar du varje attribut på en separat rad:

// ✔️ OK

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

När du använder en parameter placerar du attribut på samma rad och separerar dem med en ; avgränsare.

Tack

Dessa riktlinjer baseras på en omfattande guide till F#-formateringskonventioner av Anh-Dung Phan.