Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Dieser Artikel bietet Richtlinien, wie Sie Ihren Code formatieren, um Ihren F#-Code wie folgt zu gestalten:
- Besser lesbar
- Konform mit den Konventionen, die von Formatierungstools in Visual Studio Code und anderen Editoren angewandt werden
- Ähnlich wie anderen Code im Internet
Weitere Informationen – auch zu Namenskonventionen – finden Sie unter Codierungskonventionen und Entwurfsrichtlinien für Komponenten.
Automatische Codeformatierung
Der Codeformatierer Fantomas ist das Standardtool der F#-Community für die automatische Codeformatierung. Die Standardeinstellungen entsprechen diesem Style Guide.
Es wird dringend empfohlen, diesen Codeformatierer zu verwenden. F#-Teams sollten sich auf einheitliche Regeln für die Codeformatierung einigen und diese in der Einstellungsdatei für den Codeformatierer festhalten, die ins Teamrepository eingecheckt wird.
Allgemeine Regeln für die Formatierung
F# verwendet standardmäßig signifikanten Leerraum und ist empfindlich gegenüber Leerzeichen. Die folgenden Richtlinien sollen als Orientierungshilfen für einige Herausforderungen dienen, die dies mit sich bringt.
Verwenden Sie Leerzeichen statt Tabstopps
Wenn ein Einzug erforderlich ist, müssen Sie Leerzeichen anstelle von Tabstoppzeichen verwenden. In F#-Code werden keine Tabstoppzeichen verwendet, und der Compiler gibt einen Fehler aus, wenn außerhalb eines Zeichenfolgenliterals oder Kommentars ein Tabstoppzeichen gefunden wird.
Verwenden Sie konsistente Einrückungen
Für den Einzug ist mindestens ein Leerzeichen erforderlich. Ihre Organisation kann Codierungsstandards erstellen, um die Anzahl von Leerzeichen anzugeben, die für den Einzug bei jeder Ebene, in der Einzüge vorkommen, verwendet werden sollen - in der Regel sind das zwei, drei oder vier Leerzeichen.
Wir empfehlen vier Leerzeichen pro Einzug.
Der Einzug von Programmen ist jedoch eine subjektive Entscheidung. Abweichungen sind zwar möglich, aber die erste Regel, die Sie befolgen sollten, ist ein einheitlicher Einzug. Wählen Sie einen weit verbreiteten Indentationsstil und wenden Sie ihn systematisch in Ihrer gesamten Codebasis an.
Vermeiden von Formatierungen, die von der Namenslänge abhängig sind
Versuchen Sie, Einzüge und Ausrichtungen zu vermeiden, die empfindlich gegenüber Benennungen sind.
// ✔️ 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 _ -> ()
| ...
Die Hauptgründe hierfür sind:
- Wichtiger Code wird stark nach rechts verschoben.
- Es bleibt weniger Breite für den tatsächlichen Code übrig.
- Durch Umbenennen kann die Ausrichtung zerstört werden.
Vermeiden von überflüssigem Leerraum
Vermeiden Sie überflüssigen Leerraum in F#-Code, außer wenn er in diesem Style Guide beschrieben wird.
// ✔️ OK
spam (ham 1)
// ❌ Not OK
spam ( ham 1 )
Formatieren von Kommentaren
Verwenden Sie lieber viele Kommentare mit doppelten Schrägstrichen als Kommentarblöcke.
// 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.
*)
Kommentare sollten mit einem Großbuchstaben beginnen und wohlformulierte Ausdrücke oder Sätze darstellen.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Informationen zum Formatieren von XML-Dokumentationskommentaren finden Sie weiter unten unter „Formatieren von Deklarationen“.
Formatieren von Ausdrücken
In diesem Abschnitt wird das Formatieren verschiedener Ausdrücke erläutert.
Formatieren von Zeichenfolgenausdrücken
Zeichenfolgenliterale und interpolierte Zeichenfolgen können problemlos auf einer einzigen Zeile stehen, unabhängig davon, wie lang die Zeile ist.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
Von mehrzeiligen interpolierten Ausdrücken wird abgeraten. Binden Sie stattdessen das Ausdrucksergebnis an einen Wert, und verwenden Sie diesen in der interpolierten Zeichenfolge.
Formatieren von Tupelausdrücken
Eine Tupelinstanziierung sollte in Klammern stehen, und auf die als Trennzeichen verwendeten Kommas sollte ein einzelnes Leerzeichen folgen, z. B.: (1, 2), (x, y, z).
// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]
Es ist allgemein üblich, bei der Mustererkennung von Tupeln die Klammern auszulassen.
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
Es ist außerdem allgemein akzeptiert, die Klammern wegzulassen, wenn das Tupel der Rückgabewert einer Funktion ist.
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
Zusammenfassend sollten Sie bei der Tupelinstanziierung Klammern bevorzugen, aber bei der Verwendung von Tupel für den Musterabgleich oder als Rückgabewert ist es in Ordnung, die Klammern wegzulassen.
Formatieren von Anwendungsausdrücken
Beim Formatieren einer Funktions- oder Methodenanwendung werden Argumente auf derselben Zeile bereitgestellt, sofern die Zeilenbreite dies zulässt:
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Verwenden Sie nur dann Klammern, wenn die Argumente dies erfordern:
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)
// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)
Lassen Sie bei mehreren geschachtelten Argumenten die Leerzeichen nicht weg.
// ✔️ 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)
Laut den Standardformatierungskonventionen sollte ein Leerzeichen eingefügt werden, wenn Funktionen in Kleinbuchstaben auf Argumente in Tupeln oder Klammern angewandt werden (selbst wenn nur ein einzelnes Argument verwendet wird):
// ✔️ 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)
Gemäß den Standardformatkonventionen wird beim Anwenden groß geschriebener Methoden auf Argumente in Tupeln kein Leerraum hinzugefügt. Dies liegt daran, dass sie häufig bei der fluenten Programmierung verwendet werden:
// ✔️ 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)
Dieselben Formatierungskonventionen gelten für den Musterabgleich. F#-Stilwerte mit einheitlicher Formatierung:
// ✔️ OK - Consistent formatting for expressions and patterns
let result = Some(value)
match result with
| Some(x) -> x
| None -> 0
Möglicherweise müssen Sie Argumente an eine Funktion auf einer neuen Zeile übergeben, damit sie besser lesbar sind oder weil Argumentlisten oder -namen zu lang sind. In diesem Fall verwenden Sie einen Einzug um eine Ebene:
// ✔️ 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)
Wenn die Funktion ein einzelnes mehrzeiliges Tuplerargument akzeptiert, platzieren Sie jedes Argument auf einer neuen Zeile:
// ✔️ 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)
Wenn Argumentausdrücke kurz sind, trennen Sie die Argumente durch Leerzeichen voneinander ab, und belassen Sie sie auf einer Zeile.
// ✔️ OK
let person = new Person(a1, a2)
// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)
// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)
Wenn Argumentausdrücke lang sind, verwenden Sie neue Zeilen und eine Einrückungsebene, anstatt zur linken Klammer einzurücken.
// ✔️ 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)
Die gleichen Regeln gelten auch dann, wenn nur ein einzelnes mehrzeiliges Argument vorhanden ist, sowie bei mehrzeiligen Zeichenfolgen:
// ✔️ 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" ]
)
Formatieren von Pipelineausdrücken
Wenn Sie mehrere Zeilen verwenden, sollten die Pipelineoperatoren |> unter den Ausdrücken platziert werden, auf die sie angewendet werden.
// ✔️ 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
Für Reverse-Pipeline-Operatoren <| sollten kurze Ausdrücke in einer einzigen Zeile bleiben. Wenn die Zeilenlänge umbrochen werden muss, platzieren Sie Argumente in neuen Zeilen, und richten Sie sie konsistent aus:
// ✔️ OK - short expressions stay on one line
let result = someFunction <| arg1 <| arg2 <| arg3
// ✔️ OK - longer expressions can wrap when necessary
failwith
<| sprintf "A very long error message that exceeds reasonable line length: %s - additional details: %s"
longVariableName
anotherLongVariableName
// ✔️ OK - align continuation lines with the operator
let longResult =
someVeryLongFunctionName
<| firstVeryLongArgumentName
<| secondVeryLongArgumentName
<| thirdVeryLongArgumentName
// ❌ Not OK - unnecessary wrapping of short expressions
failwith <| sprintf "short: %s"
value
Formatieren von Lambdaausdrücken
Wenn ein Lambdaausdruck als Argument in einem mehrzeiligen Ausdruck und gefolgt von anderen Argumenten verwendet wird, platzieren Sie den Text des Lambdaausdrucks auf einer neuen Zeile, die um eine Ebene eingezogen ist:
// ✔️ OK
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")
list1
Wenn das Lambdaargument das letzte Argument in einer Funktionsanwendung ist, platzieren Sie alle Argumente bis zum Pfeil auf derselben Zeile.
// ✔️ 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}")
Auch Lambdaabgleiche sollten Sie auf ähnliche Weise behandeln.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Wenn vor dem Lambdaausdruck viele führende oder mehrzeilige Argumente stehen, sollten sämtliche Argumente um eine Ebene eingerückt werden.
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Wenn der Text eines Lambdaausdrucks mehrere Zeilen lang ist, sollten Sie erwägen, ihn in eine lokale Funktion umzugestalten.
Wenn Pipelines Lambdaausdrücke enthalten, sind diese in der Regel das letzte Argument in jeder Phase der Pipeline:
// ✔️ OK, with 4 spaces indentation
let printListWithOffsetPiped list1 =
list1
|> List.map (fun elem -> elem + 1)
|> List.iter (fun elem ->
// one indent starting from the pipe
printfn $"A very long line to format the value: %d{elem}")
// ✔️ OK, with 2 spaces indentation
let printListWithOffsetPiped list1 =
list1
|> List.map (fun elem -> elem + 1)
|> List.iter (fun elem ->
// one indent starting from the pipe
printfn $"A very long line to format the value: %d{elem}")
Wenn die Argumente eines Lambdaausdrucks nicht auf eine Zeile passen oder selbst mehrzeilig sind, platzieren Sie sie auf der nächsten Zeile, und ziehen Sie diese um eine Ebene ein.
// ✔️ 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 ()
Formatieren von trägen Ausdrücken
Wenn Sie einzeilige faule Ausdrücke schreiben, behalten Sie alles in einer Zeile bei:
// ✔️ OK
let x = lazy (computeValue())
// ✔️ OK
let y = lazy (a + b)
Platzieren Sie bei mehrzeiligen faulen Ausdrücken die öffnende Klammer in derselben Zeile wie das lazy Schlüsselwort, wobei der Textkörper des Ausdrucks eine Ebene eingezogen und die schließende Klammer am Öffnen ausgerichtet ist:
// ✔️ OK
let v =
lazy (
// some code
let x = computeExpensiveValue()
let y = computeAnotherValue()
x + y
)
// ✔️ OK
let handler =
lazy (
let connection = openConnection()
let data = fetchData connection
processData data
)
Dies folgt demselben Muster wie andere Funktionsanwendungen mit mehrlineilen Argumenten. Die öffnende Klammer bleibt bei lazy, und der Ausdruck wird um eine Ebene eingerückt.
Formatieren von arithmetischen und binären Ausdrücken
Verwenden Sie immer Leerzeichen um binäre arithmetische Ausdrücke:
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
Wenn der binäre --Operator in Kombination mit bestimmten Formatierungsoptionen nicht von Leerraum umgeben ist, könnte er als unärer --Operator interpretiert werden kann.
Der unäre --Operator sollte immer unmittelbar dem Wert vorausgehen, den er negiert:
// ✔️ OK
let negate x = -x
// ❌ Not OK
let negateBad x = - x
Das Hinzufügen eines Leerzeichens nach dem --Operator kann andere Personen verwirren.
Trennen Sie binäre Operatoren durch Leerzeichen voneinander ab. Infix-Ausdrücke können problemlos nach derselben Spalte ausgerichtet werden:
// ✔️ OK
let function1 () =
acc +
(someFunction
x.IngredientName x.Quantity)
// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Diese Regel gilt auch für Maßeinheiten in Typ- und Konstantendeklarationen:
// ✔️ 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)> }
Die folgenden Operatoren sind in der F#-Standardbibliothek definiert und sollten immer verwendet werden anstatt Entsprechungen zu definieren. Die Verwendung dieser Operatoren wird empfohlen, da sie den Code in der Regel besser lesbar und idiomatischer machen. In der folgenden Liste sind die empfohlenen F#-Operatoren zusammengefasst.
// ✔️ OK
x |> f // Forward pipeline
f <| x // Reverse 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
Formatieren von Bereichsoperatorausdrücke
Fügen Sie nur Leerzeichen um die .. Zeichenfolge hinzu, wenn ein Ausdruck nicht atomar ist.
Integer und Einzelwortbezeichner gelten als atomisch.
// ✔️ 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 ]
Diese Regeln gelten auch für die Segmentierung:
// ✔️ OK
arr[0..10]
list[..^1]
Formatieren von if-Ausdrücken
Der Einzug von Bedingungen hängt von der Größe und Komplexität der Ausdrücke ab, aus denen sie bestehen. Schreiben Sie sie in folgenden Fällen auf einer Zeile:
-
cond,e1unde2sind kurz. -
e1unde2sind selbst keineif/then/else-Ausdrücke.
// ✔️ OK
if cond then e1 else e2
Wenn es keinen else-Ausdruck gibt, wird empfohlen, den gesamten Ausdruck niemals auf einer Zeile zu schreiben. Damit soll imperativer Code besser von Funktionscode unterscheidbar bleiben.
// ✔️ OK
if a then
()
// ❌ Not OK, code formatters will reformat to the above by default
if a then ()
Wenn einer der Ausdrücke mehrzeilig ist, sollte jeder Bedingungszweig mehrzeilig sein.
// ✔️ OK
if cond then
let e1 = something()
e1
else
e2
// ❌ Not OK
if cond then
let e1 = something()
e1
else e2
Mehrere Konditionalanweisungen mit elif und else werden auf derselben Ebene wie if eingerückt, wenn sie den Regeln für einzeilige if/then/else-Ausdrücke entsprechen.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Wenn nur eine der Bedingungen oder Ausdrücke mehrzeilig ist, wird dadurch der gesamte if/then/else-Ausdruck mehrzeilig:
// ✔️ 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
Wenn eine Bedingung mehrzeilig ist oder die Standardtoleranz der Einzeilenbedingung überschreitet, sollte der Bedingungsausdruck mit einem Einzug in einer neuen Zeile stehen.
Die Schlüsselwörter if und then sollten beim Einkapseln eines langen Bedingungsausdrucks gleich ausgerichtet sein.
// ✔️ 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
Es gilt jedoch als besserer Stil, lange Bedingungen in eine let-Bindung oder separate Funktion umzugestalten:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
if performAction then
e1
else
e2
Formatieren von Vereinigungsfallausdrücken
Das Anwenden unterscheidbarer Union-Fälle folgt den gleichen Regeln wie Funktions- und Methodenanwendungen. Das heißt, da der Name groß geschrieben ist, entfernen Code-Formatierer ein Leerzeichen vor einem Tupel:
// ✔️ OK
let opt = Some("A", 1)
// OK, but code formatters will remove the space
let opt = Some ("A", 1)
Wie bei Funktionsanwendungen sollten mehrzeilige Konstruktionen mit Einrückung formatiert werden.
// ✔️ OK
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
Formatieren von Listen- und Arrayausdrücken
Sie schreiben x :: l mit Leerzeichen um den ::-Operator (:: ist ein Infix-Operator und steht daher zwischen Leerzeichen).
Bei Listen und Arrays, die auf einer einzelnen Zeile deklariert werden, sollte nach der öffnenden Klammer und vor der schließenden Klammer ein Leerzeichen stehen:
// ✔️ OK
let xs = [ 1; 2; 3 ]
// ✔️ OK
let ys = [| 1; 2; 3; |]
Verwenden Sie immer mindestens ein Leerzeichen zwischen verschiedenen klammerartigen Operatoren. Setzen Sie z. B. ein Leerzeichen zwischen [ und {.
// ✔️ 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 }]
Für Listen oder Arrays mit Tupeln gilt dieselbe Richtlinie.
Für mehrzeilige Listen und Arrays gilt eine ähnliche Regel wie für Datensätze:
// ✔️ 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 |] |]
Wie bei Datensätzen vereinfacht das Deklarieren der öffnenden und schließenden Klammern auf einer eigenen Zeile das Verschieben von Code und das Einfügen in Funktionen:
// ✔️ 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 |]
|]
Wenn ein Listen- oder Arrayausdruck auf der rechten Seite einer Bindung steht, sollten Sie bevorzugen, den Stroustrup-Stil zu verwenden.
// ✔️ 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 |]
|]
Bei Listen- oder Arrayausdrücken, die nicht die rechte Seite einer Bindung sind, sich z. B. innerhalb einer anderen Liste oder eines anderen Arrays befinden und sich über mehrere Zeilen erstrecken, sollten die Klammern auf eigenen Zeilen stehen:
// ✔️ 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
] ]
Dieselbe Regel gilt auch für Datensatztypen innerhalb von Arrays oder Listen:
// ✔️ 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
} ]
Wenn Sie Arrays und Listen programmgesteuert generieren, sollten Sie -> gegenüber do ... yield vorziehen, wenn immer ein Wert generiert wird:
// ✔️ 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 ]
In älteren Versionen von F# musste in Situationen, in denen Daten bedingt generiert wurden oder aufeinanderfolgende Ausdrücke ausgewertet werden mussten, yield angegeben werden. Lassen Sie das Schlüsselwort yield lieber weg, sofern Sie nicht mit einer älteren F#-Version kompilieren:
// ✔️ OK
let daysOfWeek includeWeekend =
[
"Monday"
"Tuesday"
"Wednesday"
"Thursday"
"Friday"
if includeWeekend then
"Saturday"
"Sunday"
]
// ❌ Not preferred - omit yield instead
let daysOfWeek' includeWeekend =
[
yield "Monday"
yield "Tuesday"
yield "Wednesday"
yield "Thursday"
yield "Friday"
if includeWeekend then
yield "Saturday"
yield "Sunday"
]
In einigen Fällen kann do...yield jedoch die Lesbarkeit verbessern. Diese Fälle, obwohl subjektiv, sollten berücksichtigt werden.
Formatieren von Datensatzausdrücken
Kurze Datensätze können auf einer Zeile geschrieben werden:
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
Bei längeren Datensätzen sollten für Bezeichnungen neue Zeilen verwendet werden:
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Stile der mehrzeiligen Klammerformatierung
Für Datensätze, die sich über mehrere Zeilen erstrecken, gibt es drei häufig verwendete Formatierungsstile: Cramped, Aligned und Stroustrup. Der Cramped-Stil ist der Standardstil für F#-Code, da er tendenziell Stile fördert, die das Parsen von Code durch den Compiler vereinfachen. Die Stile Aligned und Stroustrup vereinfachen die Neuanordnung von Membern, sodass der Code einfacher umgestaltet werden kann. Sie haben allerdings den Nachteil, dass in bestimmten Situationen möglicherweise etwas ausführlicherer Code erforderlich ist.
Cramped: Der traditionelle Standard und das Standardformat für F#-Datensätze. Öffnende Klammern stehen auf derselben Zeile wie das erste Element und schließende Klammern auf derselben Zeile wie das letzte Element.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }Aligned: Klammern stehen auf einer eigenen Zeile, ausgerichtet in derselben Spalte.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }Stroustrup: Die öffnende Klammer steht auf derselben Zeile wie die Bindung und die schließende Klammer auf einer eigenen Zeile.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Diese Formatierungsstilregeln gelten auch für Listen- und Arrayelemente.
Formatieren von Copy-and-Update-Record-Ausdrücken
Ein Copy-and-Update-Record-Ausdruck ist nach wie vor ein Record, sodass ähnliche Richtlinien gelten.
Kurze Ausdrücke können auf einer Zeile stehen:
// ✔️ OK
let point2 = { point with X = 1; Y = 2 }
Für längere Ausdrücke sollten neue Zeilen verwendet und basierend auf einer der oben genannten Konventionen formatiert werden.
// ✔️ 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 = ""
}
}
Hinweis: Wenn Sie den Stroustrup-Stil für Copy-and-Update-Ausdrücke verwenden, müssen Sie die Member weiter einziehen als den kopierten Datensatznamen:
// ✔️ 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"
}
Formatieren von Musterabgleichen
Verwenden Sie für jede Klausel eines Abgleichs | ohne Einzug. Wenn der Ausdruck kurz ist, können Sie ihn auch auf einer einzelnen Zeile schreiben, sofern die Teilausdrücke ebenfalls einfach sind.
// ✔️ 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"
Die Formatierung des Musterabgleichs sollte mit der Ausdrucksformatierung konsistent sein. Fügen Sie vor der öffnenden Klammer von Musterargumenten kein Leerzeichen hinzu:
// ✔️ OK
match x with
| Some(y) -> y
| None -> 0
// ✔️ OK
match data with
| Success(value) -> value
| Error(msg) -> failwith msg
// ❌ Not OK, pattern formatting should match expression formatting
match x with
| Some (y) -> y
| None -> 0
Verwenden Sie jedoch Leerzeichen zwischen getrennten geschweiften Argumenten in Mustern, genau wie in Ausdrücken:
// ✔️ OK - space between curried arguments
match x with
| Pattern arg (a, b) -> processValues arg a b
// ❌ Not OK - missing space between curried arguments
match x with
| Pattern arg(a, b) -> processValues arg a b
Wenn der Ausdruck auf der rechten Seite des Musterabgleichspfeils zu groß ist, verschieben Sie ihn auf die nächste Zeile und ziehen ihn um eine Ebene gegenüber match/| ein.
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Ähnlich wie bei großen if-Bedingungen sollten match-Ausdrücke, die mehrzeilig sind oder die Standardtoleranz für einzeilige Ausdrücke überschreiten, eine Einrückung und eine neue Zeile verwenden.
Die Schlüsselwörter match und with sollten beim Kapseln des langen Match-Ausdrucks gleich ausgerichtet sein.
// ✔️ OK, but better to refactor, see below
match
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
with
| X y -> y
| _ -> 0
Es gilt jedoch als besserer Stil, lange Match-Ausdrücke in eine let-Bindung oder eine separate Funktion umzugestalten.
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
match performAction with
| X y -> y
| _ -> 0
Das Ausrichten der Pfeile eines Mustervergleichs sollte vermieden werden.
// ✔️ 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
Ein Musterabgleich, der mithilfe des Schlüsselworts function eingeführt wurde, sollte um eine Ebene gegenüber dem Anfang der vorherigen Zeile eingerückt werden.
// ✔️ OK
lambdaList
|> List.map (function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
Die Verwendung von function in Funktionen, die durch let oder let rec definiert sind, sollte im Allgemeinen zugunsten von match vermieden werden. Bei Verwendung von Musterregeln sollten diese am Schlüsselwort function ausgerichtet werden:
// ✔️ 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
Formatierung von try/with-Ausdrücken
Ein Musterabgleich mit Ausnahmetypen sollte auf derselben Ebene wie with eingezogen werden.
// ✔️ 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"
Fügen Sie ein | für jeden Abschnitt hinzu, außer wenn es nur einen einzigen Abschnitt gibt.
// ✔️ 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
Formatieren benannter Argumente
Bei benannten Argumenten sollte das = von Leerzeichen eingeschlossen sein:
// ✔️ 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)
Beim Musterabgleich mit diskriminierenden Vereinigungen werden benannte Muster ähnlich formatiert, z. B.
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
Formatieren von Mutationsausdrücken
Mutationsausdrücke der Form location <- expr werden normalerweise auf einer Zeile geschrieben.
Wenn eine mehrzeilige Formatierung erforderlich ist, platzieren Sie den Ausdruck auf der rechten Seite in einer neuen Zeile.
// ✔️ 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
Formatieren von Objektausdrücken
Member von Objektausdrücken sollten an member ausgerichtet und um eine Ebene eingezogen werden.
// ✔️ 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) }
Sie ziehen möglicherweise auch in Erwägung, den Stroustrup-Stil zu verwenden.
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)
}
Leere Typdefinitionen können in einer Zeile formatiert werden:
type AnEmptyType = class end
Unabhängig von der gewählten Seitenbreite sollte = class end immer in der gleichen Zeile stehen.
Formatieren von Index-/Segmentausdrücken
Indexausdrücke dürfen keine Leerzeichen um die öffnenden und schließenden Klammern enthalten.
// ✔️ OK
let v = expr[idx]
let y = myList[0..1]
// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]
Dies gilt auch für die ältere expr.[idx]-Syntax.
// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]
// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]
Formatieren zitierter Ausdrücke
Die Trennzeichensymbole (<@, @>, <@@, @@>) sollten in separaten Zeilen platziert werden, wenn der anführungszeichenausdruck ein mehrzeiliger Ausdruck ist.
// ✔️ OK
<@
let f x = x + 10
f 20
@>
// ❌ Not OK
<@ let f x = x + 10
f 20
@>
Bei einzeiligen Ausdrücken sollten die Trennzeichensymbole auf derselben Zeile wie der Ausdruck selbst stehen.
// ✔️ OK
<@ 1 + 1 @>
// ❌ Not OK
<@
1 + 1
@>
Formatieren verketteter Ausdrücke
Wenn verkettete Ausdrücke (mit . verknüpfte Funktionsanwendungen) lang sind, schreiben Sie jeden Anwendungsaufruf auf eine eigene Zeile.
Rücken Sie die nachfolgenden Glieder in der Kette nach dem ersten Glied um eine Ebene ein.
// ✔️ OK
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
// ✔️ OK
Cli
.Wrap("git")
.WithArguments(arguments)
.WithWorkingDirectory(__SOURCE_DIRECTORY__)
.ExecuteBufferedAsync()
.Task
Das führende Glied kann aus mehreren Gliedern zusammengesetzt sein, wenn es sich um einfache Bezeichner handelt. Ein Beispiel ist das Hinzufügen eines vollqualifizierten Namespace.
// ✔️ OK
Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
Die anschließend Links sollten ebenfalls einfache Bezeichner enthalten.
// ✔️ 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))
Wenn die Argumente in einer Funktionsanwendung nicht auf eine Zeile passen, platzieren Sie jedes Argument auf einer eigenen Zeile.
// ✔️ 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
Lambdaargumente innerhalb einer Funktionsanwendung sollten auf derselben Zeile wie die öffnende ( beginnen.
// ✔️ OK
builder
.WithEnvironment()
.WithLogger(fun loggerConfiguration ->
// ...
())
// ❌ Not OK, formatting tools will reformat to the above
builder
.WithEnvironment()
.WithLogger(
fun loggerConfiguration ->
// ...
())
Formatieren von Deklarationen
In diesem Abschnitt wird das Formatieren verschiedener Deklarationen erläutert.
Einfügen von Leerzeilen zwischen Deklarationen
Trennen Sie Funktions- und Klassendefinitionen der obersten Ebene durch eine Leerzeile. Beispiel:
// ✔️ 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
Wenn ein Konstrukt XML-Dokumentationskommentare enthält, fügen Sie vor dem Kommentar eine Leerzeile ein.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Formatieren von let- und member-Deklarationen
Bei let- und member-Deklarationen wird die rechte Seite einer Bindung in der Regel entweder auf einer Zeile oder (wenn sie zu lang ist) auf einer neuen Zeile um eine Ebene eingezogen platziert.
Die folgenden Beispiele sind beispielsweise konform:
// ✔️ 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
Diese sind nicht konform:
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
let d = while f do
printfn "%A" x
Bei Instanziierungen von Datensatztypen können die Klammern auch auf eigenen Zeilen platziert werden:
// ✔️ OK
let bilbo =
{
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Sie können auch den Stroustrup-Stil in Erwägung ziehen, bei dem die öffnende { auf derselben Zeile wie der Bindungsname steht:
// ✔️ OK
let bilbo = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Trennen Sie Member durch eine Leerzeile voneinander ab, und fügen Sie einen Dokumentationskommentar hinzu:
// ✔️ OK
/// This is a thing
type ThisThing(value: int) =
/// Gets the value
member _.Value = value
/// Returns twice the value
member _.TwiceValue() = value*2
Weitere Leerzeilen können (sparsam) verwendet werden, um Gruppen verwandter Funktionen zu trennen. Leerzeilen zwischen verwandten einzeiligen Ausdrücken können ausgelassen werden (z. B. bei mehreren Platzhalterimplementierungen). Verwenden Sie Leerzeilen in Funktionen (umsichtig), um logische Abschnitte zu verdeutlichen.
Formatieren von Funktions- und Memberargumenten
Wenn Sie eine Funktion definieren, verwenden Sie Leerzeichen um jedes 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
Bei einer langen Funktionsdefinition platzieren Sie die Parameter auf neuen Zeilen und ziehen sie ein. Die Einzugsebene muss der der nachfolgenden Parameter entsprechen.
// ✔️ 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
Dies gilt auch für Mitglieder, Konstruktoren und Parameter, die Tupel verwenden:
// ✔️ 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
Wenn die Parameter durch Currying behandelt werden, platzieren Sie das =-Zeichen zusammen mit etwaigen Rückgabetypen auf einer neuen Zeile.
// ✔️ 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
Auf diese Weise können Sie zu lange Zeilen vermeiden (falls der Rückgabetyp einen langen Namen haben könnte) und die Beschädigung der Zeilen beim Hinzufügen von Parametern reduzieren.
Formatieren von Operatordeklarationen
Man kann optional Leerzeichen um eine Operatordefinition verwenden.
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
Bei benutzerdefinierten Operatoren, die mit * beginnen und mehrere Zeichen enthalten, müssen Sie am Anfang der Definition Leerraum einfügen, um Mehrdeutigkeiten beim Compiler zu vermeiden. Daher wird empfohlen, einfach die Definitionen aller Operatoren in einzelne Leerzeichen einzuschließen.
Formatieren von Datensatzdeklarationen
Bei Datensatzdeklarationen sollten Sie standardmäßig die { in der Typdefinition um vier Leerzeichen einrücken, die Bezeichnerliste auf derselben Zeile beginnen und die Member, falls vorhanden, mit dem { Token ausrichten:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
Es ist auch üblich, Klammern auf einer eigenen Zeile zu setzen, wobei die Bezeichnungen um vier zusätzliche Leerzeichen eingerückt werden.
// ✔️ OK
type PostalAddress =
{
Address: string
City: string
Zip: string
}
Sie können die { auch am Ende der ersten Zeile der Typdefinition (Stroustrup-Stil) platzieren:
// ✔️ OK
type PostalAddress = {
Address: string
City: string
Zip: string
}
Wenn zusätzliche Mitglieder benötigt werden, sollten Sie nach Möglichkeit with/end vermeiden.
// ✔️ 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
Eine Ausnahme von dieser Formatierungsregel liegt vor, wenn Sie Datensätze im Stroustrup-Stil formatieren. In diesem Fall ist aufgrund von Compilerregeln das Schlüsselwort with erforderlich, wenn Sie eine Schnittstelle implementieren oder zusätzliche Member hinzufügen möchten:
// ✔️ 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}"
Beim Hinzufügen von XML-Dokumentation für Datensatzfelder wird der Aligned- oder Stroustrup-Stil bevorzugt. Es sollten zusätzliche Leerzeichen zwischen den Mitgliedern eingefügt werden.
// ❌ 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}"
Das öffnende Token in eine neue Zeile und das schließende Token auf eine neue Zeile zu setzen, ist vorzuziehen, wenn Sie Schnittstellenimplementierungen oder Member im Datensatz deklarieren.
// ✔️ 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
Diese Regeln gelten auch für anonyme Record-Type-Aliase.
Formatieren unterscheidbarer Union-Deklarationen
Bei unterscheidbaren Union-Deklarationen rücken Sie | in der Typdefinition um vier Leerzeichen ein.
// ✔️ 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
Bei nur einer kurzen Union können Sie das führende |-Zeichen auslassen.
// ✔️ OK
type Address = Address of string
Behalten Sie bei einer längeren oder mehrzeiligen Union das | bei, und platzieren Sie jedes Feld der Union auf einer neuen Zeile mit dem trennenden * am Zeilenende.
// ✔️ 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
Fügen Sie bei Dokumentationskommentaren vor jedem ///-Kommentar eine leere Zeile ein.
// ✔️ 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
Formatieren von Literaldeklarationen
Bei F#-Literalen mit einem Literal-Attribut sollte dieses auf einer eigenen Zeile stehen und mit PascalCase-Notation benannt werden:
// ✔️ OK
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Keinesfalls sollten Sie das Attribut auf derselben Zeile wie den Wert platzieren.
Formatieren von Moduldeklarationen
Code in einem lokalen Modul muss relativ zum Modul eingezogen werden, der Code in einem Modul der obersten Ebene sollte jedoch nicht eingezogen werden. Namespace-Elemente müssen nicht eingerückt werden.
// ✔️ 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
Formatieren von do-Deklarationen
In Typdeklarationen, Moduldeklarationen und Berechnungsausdrücken ist manchmal die Verwendung von do oder do! für Vorgänge mit Nebenwirkungen erforderlich.
Wenn diese sich über mehrere Zeilen erstrecken, verwenden Sie einen Einzug und beginnen Sie auf einer neuen Zeile, um die Einzüge konsistent mit let/let! zu halten. Im folgenden Beispiel wird do in einer Klasse verwendet:
// ✔️ 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
In diesem Beispiel wird do! mit zwei Leerzeichen als Einzug verwendet (da es bei do! zufällig keinen Unterschied gegenüber vier Leerzeichen als Einzug gibt):
// ✔️ 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
}
Formatieren von Operationen für Berechnungsausdrücke
Beim Erstellen benutzerdefinierter Vorgänge für Berechnungsausdrücke wird die camelCase-Benennung empfohlen:
// ✔️ 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
}
Die modellierte Domäne legt letztendlich die Namenskonvention fest. Wenn eine andere Konvention idiomatisch ist, sollte stattdessen diese verwendet werden.
Wenn der Rückgabewert eines Ausdrucks ein Berechnungsausdruck ist, sollten Sie das Schlüsselwort des Berechnungsausdrucks in eine eigene Zeile setzen.
// ✔️ OK
let foo () =
async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Sie können auch den Berechnungsausdruck auf derselben Zeile wie den Bindungsnamen platzieren:
// ✔️ OK
let foo () = async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Unabhängig von Ihren Vorlieben sollten Sie stets versuchen, in Ihrer gesamten Codebasis konsistent zu bleiben. Es kann möglich sein, dass Sie diese Präferenz in Formatierern festlegen können, um sie konsistent zu halten.
Formatieren von Typen und Typanmerkungen
In diesem Abschnitt werden das Formatieren von Typen und Typanmerkungen besprochen. Dies schließt das Formatieren von Signaturdateien mit der Erweiterung .fsi ein.
Bevorzugen Sie die Präfix-Syntax für generische Typen (Foo<T>) mit einigen spezifischen Ausnahmen.
F# erlaubt sowohl beim Schreibens generischer Typen den Postfixstil (z. B. int list) als auch den Präfixstil (z. B. list<int>).
Der Postfixstil kann nur mit einem einzigen Typargument verwendet werden.
Bevorzugen Sie immer den .NET-Stil mit Ausnahme von sechs bestimmten Typen:
- Verwenden Sie für F#-Listen den Postfixstil:
int listanstelle vonlist<int>. - Verwenden Sie für F#-Optionen den Postfixstil:
int optionanstelle vonoption<int>. - Verwenden Sie für F#-Wertoptionen den Postfixstil:
int voptionanstelle vonvoption<int>. - Verwenden Sie für F#-Arrays den Postfixstil:
int arrayanstelle vonarray<int>oderint[]. - Verwenden Sie für Verweiszellen
int refanstelle vonref<int>oderRef<int>. - Verwenden Sie für F#-Sequenzen das Postfix-Formular:
int seqanstelle vonseq<int>.
Verwenden Sie für alle anderen Typen die Präfixform.
Formatieren von Funktionstypen
Setzen Sie beim Definieren der Signatur einer Funktion Leerzeichen um das Symbol ->:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Formatieren von Wert- und Argumenttypannotationen
Wenn Sie Werte oder Argumente mit Typanmerkungen definieren, verwenden Sie Leerzeichen nach dem :-Symbol, aber nicht davor:
// ✔️ 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
Formatierung von mehrzeiligen Typanmerkungen
Wenn eine Typanmerkung lang oder mehrzeilig ist, setzen Sie sie in die nächste Zeile und rücken Sie sie um eine Stufe ein.
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 anonyme Inlinedatensatztypen können Sie auch den Stroustrup-Stil verwenden:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Formatierung von Rückgabetypannotationen
Verwenden Sie in Rückgabetypanmerkungen von Funktionen oder Membern Leerzeichen vor und hinter dem :-Symbol:
// ✔️ OK
let myFun (a: decimal) b c : decimal = a + b + c
type C() =
member _.SomeMethod(x: int) : int = 1
// ❌ Not OK
let myFunBad (a: decimal) b c:decimal = a + b + c
let anotherFunBad (arg: int): unit = ()
type C() =
member _.SomeMethodBad(x: int): int = 1
Formatieren von Typen in Signaturen
Beim Schreiben vollständiger Funktionstypen in Signaturen ist es manchmal erforderlich, die Argumente auf mehrere Zeilen aufzuteilen. Der Rückgabetyp ist immer eingerückt.
Bei einer Tupelfunktion werden die Argumente durch * am Ende jeder Zeile getrennt.
Angenommen, Sie haben eine Funktion mit der folgenden Implementierung:
let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
In der zugehörigen Signaturdatei (Erweiterung .fsi) kann die Funktion wie folgt formatiert werden, wenn eine mehrzeilige Formatierung erforderlich ist:
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list
Sehen Sie sich auch diese zusammengesetzte Funktion an:
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
In der zugehörigen Signaturdatei werden die -> am Ende jeder Zeile platziert:
// ✔️ OK
val SampleCurriedFunction:
arg1: string ->
arg2: string ->
arg3: int ->
arg4: int ->
int list
Gehen Sie von eine Funktion aus, die eine Mischung aus zusammengesetzten und Tupelargumenten akzeptiert:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
In der zugehörigen Signaturdatei werden die Typen, denen ein Tupel vorangestellt ist, eingerückt.
// ✔️ 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
Diese Regeln gelten auch für Mitglieder in Typsignaturen:
type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string
Formatieren expliziter generischer Typargumente und Einschränkungen
Die folgenden Richtlinien gelten für Funktionsdefinitionen, Memberdefinitionen, Typdefinitionen und Funktionsanwendungen.
Schreiben Sie generische Typargumente und Einschränkungen auf einer einzelnen Zeile, sofern sie nicht zu lang sind:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Wenn die generische Typargumente/Einschränkungen nicht zusammen mit den Funktionsparametern auf eine Zeile passen, aber die Typparameter/Einschränkungen allein, platzieren Sie die Parameter auf neuen Zeilen:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
param
=
// function body
Wenn die Typparameter oder Einschränkungen zu lang sind, unterbrechen Sie sie und richten sie wie unten gezeigt aus. Platzieren Sie die Liste der Typparameter unabhängig von ihrer Länge auf derselben Zeile wie die Funktion. Bei Einschränkungen platzieren Sie when auf der ersten Zeile, und schreiben Sie jede Einschränkung unabhängig von ihrer Länge auf einer eigenen Zeile bei. Setzen Sie am Ende der letzten Zeile ein >-Zeichen. Richten Sie die Einschränkungen um eine Ebene ein.
// ✔️ 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
Wenn die Typparameter/Einschränkungen auf mehrere Zeilen verteilt sind, aber keine normalen Funktionsparameter vorhanden sind, setzen Sie das = in eine neue Zeile:
// ✔️ 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
Für Funktionsanwendungen gelten dieselben Regeln:
// ✔️ 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
Formatieren der Vererbung
Die Argumente für den Basisklassenkonstruktor sind in der Argumentliste in der inherit-Klausel enthalten.
Platzieren Sie die inherit-Klausel auf einer neuen Zeile, die um eine Ebene eingezogen ist.
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)
Einen lange oder mehrzeiligen Konstruktor platzieren Sie auf der nächsten Zeile, die Sie um eine Ebene einziehen.
Formatieren Sie solche mehrzeiligen Konstruktoren gemäß den Regeln für mehrzeilige Funktionsanwendungen.
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
""")
Formatierung des primären Konstruktors
In Standardformatkonventionen wird zwischen dem Typnamen und den Klammern für den primären Konstruktor kein Leerzeichen hinzugefügt.
// ✔️ 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
Mehrere Konstruktoren
Wenn die inherit-Klausel Teil eines Datensatzes und kurz ist, platzieren Sie sie auf derselben Zeile.
Wenn diese lang oder mehrzeilig ist, platzieren Sie sie in der nächsten Zeile, eingezogen um eine Ebene.
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 }
Formatieren von Attributen
Attribute werden über einem Konstrukt angeordnet:
// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...
// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x
// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
Sie sollten sich mit der XML-Dokumentation befassen.
// ✔️ OK
/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x
Formatieren von Attributen in Parametern
Attribute können auch in Parametern platziert werden. In diesem Fall platzieren Sie sie auf derselben Zeile wie der Parameter und vor dem Namen:
// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)
Formatieren mehrerer Attribute
Wenn auf ein Konstrukt, das kein Parameter ist, mehrere Attribute angewandt werden, platzieren Sie diese einzeln auf separaten Zeilen:
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
Wenn sie auf einen Parameter angewandt werden, platzieren Sie die Attribute auf derselben Zeile und trennen sie durch ein ;-Zeichen ab.
Danksagungen
Diese Richtlinien basieren auf A comprehensive guide to F# Formatting Conventions (Umfassender Leitfaden zu F#-Formatierungskonventionen) von Anh-Dung Phan.