F#-kódformázási irányelvek
Ez a cikk útmutatást nyújt a kód úgy történő formázásához, hogy az F#-kód a következő legyen:
- Olvashatóbb
- A Visual Studio Code és más szerkesztők formázási eszközei által alkalmazott konvencióknak megfelelően
- Hasonló más online kódhoz
Lásd még a kódolási konvenciókat és az összetevők tervezési irányelveit, amelyek az elnevezési konvenciókat is ismertetik.
Automatikus kódformázás
A Fantomas kódformázó az automatikus kódformázás F# közösségi szabványeszköze. Az alapértelmezett beállítások megfelelnek ennek a stíluskalauznak.
Határozottan javasoljuk ennek a kódformázásnak a használatát. Az F#-csapatokban a kódformázási specifikációkat meg kell egyeztetni és kodifikálni kell a csoportadattárba bevett kódformázási fájlban.
Általános formázási szabályok
Az F# alapértelmezés szerint jelentős üres területet használ, és a szabad terület érzékeny. Az alábbi iránymutatások útmutatást nyújtanak arra vonatkozóan, hogyan háríthatók el az ilyen jellegű kihívások.
Szóközök használata nem lapok
Ha behúzásra van szükség, szóközöket kell használnia, nem tabulátorokat. Az F#-kód nem használ lapokat, és a fordító hibaüzenetet ad, ha egy tabulátorkaraktere egy sztringkonstanson vagy megjegyzésen kívül jelenik meg.
Konzisztens behúzás használata
Behúzáskor legalább egy szóköz szükséges. A szervezet kódolási szabványokat hozhat létre a behúzáshoz használandó szóközök számának meghatározásához; Két, három vagy négy behúzási szóköz minden olyan szinten, ahol a behúzás történik, jellemző.
Behúzásonként négy szóközt ajánlunk.
Igaz, a programok behúzása szubjektív kérdés. A változatok rendben vannak, de az első szabály, amelyet követnie kell, a behúzás konzisztenciája. Válasszon egy általánosan elfogadott behúzási stílust, és használja azt szisztematikusan a kódbázisban.
Kerülje a névhosszra érzékeny formázást
Kerülje az elnevezésre érzékeny behúzást és igazítást:
// ✔️ 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 _ -> ()
| ...
Ennek elkerülésének elsődleges okai a következők:
- A fontos kód jobbra van helyezve
- A tényleges kódnak kevesebb szélessége maradt
- Az átnevezés megszakíthatja az igazítást
Kerülje a felesleges szóközt
Az F#-kódban kerülje a felesleges szóközt, kivéve az ebben a stíluskalauzban leírtakat.
// ✔️ OK
spam (ham 1)
// ❌ Not OK
spam ( ham 1 )
Megjegyzések formázása
Több dupla perjeles megjegyzés előnyben részesítve a blokkbejegyzéseket.
// 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.
*)
A megjegyzéseknek nagybetűssé kell tenni az első betűt, és jól formázott kifejezéseknek vagy mondatoknak kell lenniük.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Az XML-dokumentum megjegyzéseinek formázásához lásd alább a "Formázási deklarációk" című témakört.
Formázási kifejezések
Ez a szakasz a különböző típusú formázási kifejezéseket ismerteti.
Sztringkifejezések formázása
A sztringkonstansok és az interpolált sztringek csak egyetlen sorban hagyhatók, függetlenül attól, hogy mennyi a vonal.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
A többsoros interpolált kifejezések nem támogatottak. Ehelyett kösse össze a kifejezés eredményét egy értékkel, és használja azt az interpolált sztringben.
Rekordkifejezések formázása
A rekordpéldányt zárójelbe kell tenni, és a benne lévő elválasztó vesszőket egyetlen szóközzel kell követni, például: (1, 2)
, (x, y, z)
.
// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]
Általánosan elfogadott, hogy kihagyja a zárójeleket a csuplok mintaegyezésében:
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
Gyakran elfogadott a zárójelek kihagyása is, ha a rekord egy függvény visszatérési értéke:
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
Összefoglalva, előnyben részesítse a zárójeles tuple-példányokat, de ha a mintaegyeztetéshez vagy a visszatérési értékhez használ rekordokat, akkor érdemes elkerülni a zárójeleket.
Alkalmazáskifejezések formázása
Függvény vagy metódusalkalmazás formázásakor az argumentumok ugyanabban a sorban jelennek meg, amikor a sorszélesség lehetővé teszi:
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Hagyja ki a zárójeleket, kivéve, ha az argumentumok megkövetelik őket:
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)
// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)
Ne hagyja ki a szóközöket, ha több, curriált argumentummal rendelkező argumentumot invokált:
// ✔️ 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)
Az alapértelmezett formázási konvenciókban a rendszer szóközt ad hozzá, amikor kisbetűs függvényeket alkalmaz a csatolt vagy zárójeles argumentumokra (még akkor is, ha egyetlen argumentumot használ):
// ✔️ 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)
Az alapértelmezett formázási konvenciókban a rendszer nem ad hozzá szóközt, amikor nagybetűs metódusokat alkalmaz a tupled argumentumokra. Ennek az az oka, hogy ezeket gyakran használják folyékony programozással:
// ✔️ 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)
Előfordulhat, hogy az argumentumokat át kell adnia egy új sorban lévő függvénynek az olvashatóság függvényében, vagy mert az argumentumok listája vagy az argumentumnevek túl hosszúak. Ebben az esetben egy szint behúzása:
// ✔️ 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)
Ha a függvény egyetlen többsoros összefűző argumentumot használ, helyezze az egyes argumentumokat egy új sorba:
// ✔️ 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)
Ha az argumentumkifejezések rövidek, különítse el az argumentumokat szóközökkel, és tartsa egy sorban.
// ✔️ OK
let person = new Person(a1, a2)
// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)
// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)
Ha az argumentumkifejezések hosszúak, használjon új sorokat, és egy szint behúzásával ne a bal oldali zárójelbe.
// ✔️ 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)
Ugyanezek a szabályok akkor is érvényesek, ha csak egyetlen többsoros argumentum létezik, beleértve a többsoros sztringeket is:
// ✔️ 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" ]
)
Folyamatkifejezések formázása
Ha több sort használ, a folyamatkezelőknek |>
az általuk használt kifejezések alá kell lépniük.
// ✔️ 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
Lambda-kifejezések formázása
Ha a lambda kifejezést argumentumként használják egy többsoros kifejezésben, és más argumentumok követik, helyezze egy lambda kifejezés törzsét egy új sorra, egy szinttel behúzva:
// ✔️ OK
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")
list1
Ha a lambda argumentum egy függvényalkalmazás utolsó argumentuma, helyezze az összes argumentumot addig, amíg a nyíl ugyanarra a sorra nem kerül.
// ✔️ 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}")
A lambda-nak hasonló módon kell kezelnie.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Ha a lambda előtt számos sorvezető vagy többsoros argumentum van behúzva, az összes argumentumot egy szinttel kell behúzni.
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Ha egy lambda-kifejezés törzse több sor hosszú, érdemes megfontolni, hogy egy helyi hatókörű függvényre bontsa át.
Ha a folyamatok lambdakifejezéseket tartalmaznak, az egyes lambdakifejezések általában az utolsó argumentumok a folyamat minden szakaszában:
// ✔️ 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}")
Ha a lambda argumentumai nem férnek el egyetlen sorban, vagy maguk is többsorosak, helyezze őket a következő sorba, egy szinttel behúzva.
// ✔️ 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 ()
Aritmetikai és bináris kifejezések formázása
A bináris aritmetikai kifejezések körül mindig használjon fehér szóközt:
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
Ha nem sikerül körülvenni egy bináris -
operátort, bizonyos formázási lehetőségekkel kombinálva a függvényt unárisként -
értelmezheti.
A nem kötelező -
operátorokat mindig azonnal követnie kell az általuk tagadott értéknek:
// ✔️ OK
let negate x = -x
// ❌ Not OK
let negateBad x = - x
Ha az operátor után -
szóköz karaktert ad hozzá, az zavart okozhat mások számára.
Bináris operátorok elkülönítése szóközök szerint. Az infix kifejezések rendben vannak ugyanazon az oszlopon való sorbaálláshoz:
// ✔️ OK
let function1 () =
acc +
(someFunction
x.IngredientName x.Quantity)
// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Ez a szabály a típusok és állandó széljegyzetek mértékegységére is vonatkozik:
// ✔️ 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)> }
A következő operátorok az F# standard kódtárban vannak definiálva, és egyenértékűek definiálása helyett használhatók. Ezek az operátorok használata ajánlott, mivel a kód olvashatóbbá és idiomatikusabbá teszi a kódot. Az alábbi lista az ajánlott F#-operátorokat foglalja össze.
// ✔️ 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
Tartomány operátorkifejezéseinek formázása
Csak akkor adjon hozzá szóközöket, ..
ha az összes kifejezés nem atomi.
Az egész számok és az egyszavas azonosítók atominak minősülnek.
// ✔️ 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 ]
Ezek a szabályok a szeletelésre is vonatkoznak:
// ✔️ OK
arr[0..10]
list[..^1]
Formázás, ha a kifejezések
A feltételes kifejezések behúzása az őket alkotó kifejezések méretétől és összetettségétől függ. Írja őket egy sorba, amikor:
cond
,e1
ése2
rövidek.e1
ése2
nemif/then/else
maguk a kifejezések.
// ✔️ OK
if cond then e1 else e2
Ha a másik kifejezés hiányzik, javasoljuk, hogy soha ne írjon a teljes kifejezést egy sorba. Ennek célja, hogy megkülönböztesse az imperatív kódot a funkcionálistól.
// ✔️ OK
if a then
()
// ❌ Not OK, code formatters will reformat to the above by default
if a then ()
Ha bármelyik kifejezés többsoros, minden feltételes ágnak többsorosnak kell lennie.
// ✔️ OK
if cond then
let e1 = something()
e1
else
e2
// ❌ Not OK
if cond then
let e1 = something()
e1
else e2
Több feltételes elif
else
feltétel ugyanazon a hatókörön belül van behúzva, mint if
amikor az egysoros if/then/else
kifejezések szabályait követik.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Ha bármelyik feltétel vagy kifejezés többsoros, a teljes if/then/else
kifejezés többsoros:
// ✔️ 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
Ha egy feltétel többsoros vagy meghaladja az egysoros alapértelmezett tűréshatárt, a feltételkifejezésnek egy behúzást és egy új sort kell használnia.
A if
hosszú feltételkifejezés beágyazásakor a kulcsszónak és then
a kulcsszónak egymáshoz kell igazodnia.
// ✔️ 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
Azonban jobb stílus a hosszú feltételek újrabontása egy let kötésre vagy különálló függvényre:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
if performAction then
e1
else
e2
Egyesítő kis- és nagybetűk formázása
A diszkriminált uniós esetek alkalmazása ugyanazokat a szabályokat követi, mint a függvény- és metódusalkalmazások. Ez azt jelzi, hogy mivel a név nagybetűs, a kódformátum-formázók eltávolítanak egy szóközt a rekord előtt:
// ✔️ OK
let opt = Some("A", 1)
// OK, but code formatters will remove the space
let opt = Some ("A", 1)
A függvényalkalmazásokhoz hasonlóan a több sorra osztott szerkezeteknek is behúzást kell használniuk:
// ✔️ OK
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
Lista és tömbkifejezések formázása
Írás x :: l
szóközökkel az ::
operátor körül (::
egy infix operátor, ezért szóközökkel körülvéve).
Az egy sorban deklarált listáknak és tömböknek szóközzel kell rendelkezniük a nyitó zárójel és a záró zárójel előtt:
// ✔️ OK
let xs = [ 1; 2; 3 ]
// ✔️ OK
let ys = [| 1; 2; 3; |]
Mindig használjon legalább egy szóközt két különböző kapcsos operátor között. Például hagyjon szóközt az a [
és a {
között.
// ✔️ 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 }]
Ugyanez az útmutató vonatkozik a listákra vagy a csuplok tömbjeire is.
A több sorra osztott listák és tömbök a rekordokhoz hasonló szabályt követnek:
// ✔️ 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 |] |]
A rekordokhoz hasonlóan a nyitó és záró zárójelek saját sorban való deklarálása megkönnyíti a kód mozgatását és a függvényekbe való beásást:
// ✔️ 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 |]
|]
Ha egy lista- vagy tömbkifejezés a kötés jobb oldala, érdemes lehet stílust használnia Stroustrup
:
// ✔️ 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 |]
|]
Ha azonban egy lista- vagy tömbkifejezés nem a kötés jobb oldala, például ha egy másik listában vagy tömbben van, ha a belső kifejezésnek több sorra kell kiterjednie, a zárójeleknek a saját vonalaikra kell lépniük:
// ✔️ 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
] ]
Ugyanez a szabály vonatkozik a tömbök/listák rekordtípusaira:
// ✔️ 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
} ]
Tömbök és listák programozott létrehozásakor előnyben kell részesedni ->
do ... yield
, ha egy érték mindig létre van hozva:
// ✔️ 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 ]
Az F# régebbi verzióira van szükség yield
olyan helyzetekben, amikor az adatok feltételesen hozhatók létre, vagy egymást követő kifejezések kiértékelhetők. Inkább kihagyja ezeket a yield
kulcsszavakat, hacsak nem kell lefordítania egy régebbi F#-nyelvi verzióval:
// ✔️ 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"
]
Bizonyos esetekben do...yield
segíthet az olvashatóságban. Ezeket az eseteket, bár szubjektív, figyelembe kell venni.
Rekordkifejezések formázása
A rövid rekordok egy sorban írhatók:
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
A hosszabb rekordoknak új sorokat kell használniuk a címkékhez:
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Többsoros zárójel formázási stílusai
A több sorra kiterjedő rekordok esetében három gyakran használt formázási stílus létezik: Cramped
, Aligned
és Stroustrup
. Az Cramped
F#-kód alapértelmezett stílusa a stílus, mivel általában olyan stílusokat részesít előnyben, amelyek lehetővé teszik a fordító számára a kód egyszerű elemzését. Mind Aligned
a Stroustrup
stílusok lehetővé teszik a tagok egyszerűbb átrendezését, ami a kód könnyebb újrabontását eredményezheti, azzal a hátránysal, hogy bizonyos helyzetekben kissé részletesebb kódra lehet szükség.
Cramped
: Az előzményszabvány és az alapértelmezett F# rekordformátum. A nyitó zárójelek ugyanazon a sorban haladnak, mint az első tag, a záró zárójel ugyanazon a sorban, mint az utolsó tag.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Aligned
: A szögletes zárójelek mindegyike saját vonalat kap, ugyanarra az oszlopra igazítva.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }
Stroustrup
: A nyitó zárójel ugyanazon a vonalon halad, mint a kötés, a záró zárójel saját vonalat kap.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
A lista- és tömbelemekre ugyanazok a formázási stílusszabályok vonatkoznak.
Rekordkifejezések másolása és frissítése formázása
A másolási és frissítési rekordkifejezések továbbra is rekordnak számítanak, ezért hasonló irányelvek érvényesek.
A rövid kifejezések egy sorban elférnek:
// ✔️ OK
let point2 = { point with X = 1; Y = 2 }
A hosszabb kifejezéseknek új sorokat kell használniuk, és a fent említett konvenciók egyikén alapuló formátumot kell használniuk:
// ✔️ 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 = ""
}
}
Megjegyzés: Ha stílust használ Stroustrup
a másolási és frissítési kifejezésekhez, a másolt rekordnéven kívül be kell húznia a tagokat:
// ✔️ 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"
}
Formázási minta egyeztetése
Használjon behúzás nélküli egyezés minden záradékához egy-egyezést |
. Ha a kifejezés rövid, akkor érdemes lehet egyetlen sort használni, ha az egyes alkifejezések is egyszerűek.
// ✔️ 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"
Ha a mintaillesztési nyíl jobb oldalán lévő kifejezés túl nagy, mozgassa a következő sorba, és húzza egy lépéssel a nyilat.match
/|
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Ha egy egyező kifejezés többsoros, vagy meghaladja az egysoros alapértelmezett tűréshatárt, a nagy feltételekhez hasonlóan az egyező kifejezésnek egy behúzást és egy új sort kell használnia.
A match
hosszú egyezés kifejezés beágyazásakor a kulcsszónak with
igazodnia kell.
// ✔️ OK, but better to refactor, see below
match
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
with
| X y -> y
| _ -> 0
Azonban jobb stílus a hosszú egyezések kifejezéseinek újrabontása egy let kötéssel vagy különálló függvénnyel:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
match performAction with
| X y -> y
| _ -> 0
Kerülni kell a mintaegyezés nyílainak igazítását.
// ✔️ 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
A kulcsszó function
használatával bevezetett mintaegyeztetésnek egy szintet kell behúznia az előző sor elejéről:
// ✔️ OK
lambdaList
|> List.map (function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
A függvények function
let
let rec
használatát általában el kell kerülni match
a . Használat esetén a mintaszabályoknak a következő kulcsszóhoz function
kell igazodniuk:
// ✔️ 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
Formázási kísérlet/kifejezésekkel
A kivételtípushoz tartozó mintamegfeleltetéseket a kivételtípussal megegyező szinten with
kell behúzni.
// ✔️ 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"
Adjon hozzá egy-egy |
záradékot, kivéve, ha csak egyetlen záradék van:
// ✔️ 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
Elnevezett argumentumok formázása
A névvel ellátott argumentumoknak szóközöknek kell körülvennie a következőt =
:
// ✔️ 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)
Ha a mintaegyeztetés diszkriminált egyesítésekkel történik, a névvel ellátott minták formázása hasonlóan történik, például.
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
Mutációs kifejezések formázása
A mutációs location <- expr
kifejezések általában egy sorban vannak formázva.
Ha többsoros formázásra van szükség, helyezze a jobb oldali kifejezést egy új sorra.
// ✔️ 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
Objektumkifejezések formázása
Az objektumkifejezések tagjainak egy szinttel kell igazodniuk member
a behúzáshoz.
// ✔️ 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) }
A stílust is előnyben részesítheti Stroustrup
:
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)
}
Az üres típusdefiníciók egy sorban formázhatók:
type AnEmptyType = class end
A választott oldalszélességtől = class end
függetlenül mindig ugyanabban a sorban kell lennie.
Index-/szeletkifejezések formázása
Az indexkifejezések nem tartalmazhatnak szóközöket a nyitó és záró zárójelek körül.
// ✔️ OK
let v = expr[idx]
let y = myList[0..1]
// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]
Ez a régebbi expr.[idx]
szintaxisra is vonatkozik.
// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]
// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]
Idézett kifejezések formázása
A határolójeleket (<@
, , @>
, <@@
) @@>
külön sorokba kell helyezni, ha az idézett kifejezés többsoros kifejezés.
// ✔️ OK
<@
let f x = x + 10
f 20
@>
// ❌ Not OK
<@ let f x = x + 10
f 20
@>
Az egysoros kifejezésekben a határolójeleket ugyanazon a vonalon kell elhelyezni, mint maga a kifejezés.
// ✔️ OK
<@ 1 + 1 @>
// ❌ Not OK
<@
1 + 1
@>
Láncolt kifejezések formázása
Ha a láncolt kifejezések (a .
függvényalkalmazások összefonódnak ) hosszúak, helyezze az egyes alkalmazáshívásokat a következő sorba.
A lánc későbbi hivatkozásainak behúzása egy szinttel a bevezető hivatkozás után.
// ✔️ OK
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
// ✔️ OK
Cli
.Wrap("git")
.WithArguments(arguments)
.WithWorkingDirectory(__SOURCE_DIRECTORY__)
.ExecuteBufferedAsync()
.Task
A vezető hivatkozás több hivatkozásból állhat, ha egyszerű azonosítók. Például egy teljesen minősített névtér hozzáadása.
// ✔️ OK
Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
Az ezt követő hivatkozásoknak egyszerű azonosítókat is tartalmazniuk kell.
// ✔️ 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))
Ha a függvényalkalmazásban lévő argumentumok nem férnek el a sor többi részén, minden argumentumot a következő sorba kell helyezni.
// ✔️ 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
A lambda argumentumoknak a függvényalkalmazáson belül a nyitóval (
megegyező sorban kell kezdődnie.
// ✔️ OK
builder
.WithEnvironment()
.WithLogger(fun loggerConfiguration ->
// ...
())
// ❌ Not OK, formatting tools will reformat to the above
builder
.WithEnvironment()
.WithLogger(
fun loggerConfiguration ->
// ...
())
Formázási deklarációk
Ez a szakasz a különböző típusú formázási deklarációkat ismerteti.
Üres sorok hozzáadása deklarációk között
Különítse el a legfelső szintű függvényeket és osztálydefiníciókat egyetlen üres sortal. Példa:
// ✔️ 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
Ha egy szerkezethez XML-dokumentum megjegyzései vannak, adjon hozzá egy üres sort a megjegyzés elé.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Formázási let és tagdeklarációk
Formázás let
és member
deklarációk esetén a kötés jobb oldala általában vagy egy sorra, vagy (ha túl hosszú) egy szinten behúzott új sorra kerül.
A következő példák például megfelelőek:
// ✔️ 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
Ezek nem megfelelőek:
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
let d = while f do
printfn "%A" x
A rekordtípus-példányok a szögletes zárójeleket is elhelyezhetik a saját vonalaikon:
// ✔️ OK
let bilbo =
{
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
A stílust is előnyben részesítheti Stroustrup
, ha a megnyitás {
a kötés nevével azonos vonalon történik:
// ✔️ OK
let bilbo = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Különítse el a tagokat egyetlen üres sortal és dokumentummal, és adjon hozzá egy dokumentációs megjegyzést:
// ✔️ OK
/// This is a thing
type ThisThing(value: int) =
/// Gets the value
member _.Value = value
/// Returns twice the value
member _.TwiceValue() = value*2
További üres sorok használhatók (takarékosan) a kapcsolódó függvények csoportjainak elkülönítéséhez. Üres sorokat hagyhat ki egy csomó kapcsolódó egysoros (például próbabábu-implementációk) között. Használjon üres sorokat a függvényekben, takarékosan, a logikai szakaszok jelzéséhez.
Formázási függvény és tagargumentumok
Függvények definiálásakor használjon üres szóközt az egyes argumentumok köré.
// ✔️ 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
Ha hosszú függvénydefinícióval rendelkezik, helyezze a paramétereket az új sorokra, és a behúzásukkal egyezzen a következő paraméter behúzási szintjével.
// ✔️ 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
Ez a tagokra, konstruktorokra és paraméterekre is vonatkozik, amelyek a következő műveletet használják:
// ✔️ 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
Ha a paraméterek curriáltak, helyezze a =
karaktert a visszatérési típussal együtt egy új sorba:
// ✔️ 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
Így elkerülheti a túl hosszú sorokat (abban az esetben, ha a visszatérési típus hosszú névvel rendelkezik), és a paraméterek hozzáadásakor kevesebb sorsérülést okoz.
Formázási operátor deklarációi
Ha szeretné, használjon üres területet egy operátordefiníció körül:
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
Minden olyan egyéni operátor esetében, amely egynél több karakterből *
áll, a fordító kétértelműségének elkerülése érdekében fehér szóközt kell hozzáadnia a definíció elejéhez. Ezért azt javasoljuk, hogy egyszerűen vegye körül az összes operátor definícióját egyetlen szóköz karakterrel.
Rekorddeklarációk formázása
Rekorddeklarációk esetén alapértelmezés szerint négy szóközzel kell behúzni a {
típusdefiníciót, ugyanabban a sorban elindítani a címkelistát, és a tagokat (ha vannak) a {
jogkivonathoz igazítani:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
Az is gyakori, hogy inkább szögletes zárójeleket helyeznek a saját vonalukra, és a címkéket további négy szóköz húzja be:
// ✔️ OK
type PostalAddress =
{
Address: string
City: string
Zip: string
}
A típusdefiníció (Stroustrup
stílus) első sorának végén is elhelyezheti {
a következőt:
// ✔️ OK
type PostalAddress = {
Address: string
City: string
Zip: string
}
Ha további tagokra van szükség, ne használja with
/end
, ha lehetséges:
// ✔️ 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
Ez alól a stílusszabály alól kivételt képeznek a rekordok stílus szerinti formázása Stroustrup
. Ebben az esetben a fordítószabályok miatt a with
kulcsszóra akkor van szükség, ha egy felületet szeretne implementálni, vagy további tagokat szeretne hozzáadni:
// ✔️ 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}"
Ha xml-dokumentációt ad hozzá a rekordmezőkhöz, Aligned
vagy Stroustrup
a stílust részesíti előnyben, és további szóközt kell hozzáadni a tagok között:
// ❌ 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}"
A nyitó jogkivonat új sorra helyezése és a záró jogkivonat új sorra helyezése akkor előnyös, ha felületi implementációkat vagy tagokat deklarál a rekordon:
// ✔️ 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
Ugyanezek a szabályok vonatkoznak a névtelen rekordtípus-aliasokra is.
Diszkriminált egyesítő deklarációk formázása
Hátrányos megkülönböztetés esetén a típusdefiníciót négy szóközzel kell behúzni |
:
// ✔️ 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
Ha egyetlen rövid egyesítés van, kihagyhatja a vezetőt |
.
// ✔️ OK
type Address = Address of string
Hosszabb vagy többsoros egyesítés esetén tartsa az |
egyesítő mezőket egy új sorban, és helyezze el az egyes sorok végén lévő elválasztást *
.
// ✔️ 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
Dokumentációs megjegyzések hozzáadásakor minden megjegyzés előtt ///
használjon egy üres sort.
// ✔️ 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
Literális deklarációk formázása
Az attribútumot használó F#-literáloknak az Literal
attribútumot a saját sorukra kell helyeznie, és PascalCase-elnevezést kell használniuk:
// ✔️ OK
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Ne helyezze az attribútumot ugyanabba a sorba, mint az érték.
Formázási modul deklarációi
A helyi modul kódját be kell húzni a modulhoz képest, de a legfelső szintű modul kódját nem szabad behúzni. A névtérelemeket nem kell behúzni.
// ✔️ 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
Formázási do deklarációk
Típusdeklarációkban, moduldeklarációkban és számítási kifejezésekben a mellékhatás-műveletek használata do
vagy do!
néha szükséges.
Ha ezek több sorra is kiterjednek, a behúzással és egy új vonallal konzisztenssé kell tenni a behúzást let
/let!
. Íme egy példa az osztályban való használatra do
:
// ✔️ 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
Íme egy példa do!
két behúzási szóköz használatával (mivel do!
négy behúzási szóköz használatakor véletlenül nincs különbség a megközelítések között):
// ✔️ 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
}
Számítási kifejezések formázása
A számítási kifejezések egyéni műveleteinek létrehozásakor a CamelCase elnevezés használata javasolt:
// ✔️ 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
}
A modellezett tartománynak végső soron az elnevezési konvenciót kell vezetnie. Ha egy másik konvenciót használ, akkor inkább ezt a konvenciót kell használni.
Ha egy kifejezés visszatérési értéke számítási kifejezés, inkább a számítási kifejezés kulcsszónevét helyezze a saját sorára:
// ✔️ OK
let foo () =
async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
A számítási kifejezést a kötés nevével megegyező sorba is helyezheti:
// ✔️ OK
let foo () = async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Bármelyiket is szeretné, törekedjen arra, hogy konzisztens maradjon a kódbázisban. A formázók lehetővé tehetik, hogy ezt a beállítást konzisztensként adja meg.
Formázási típusok és szövegjegyzetek
Ez a szakasz a formázási típusokat és a szövegjegyzeteket ismerteti. Ide tartoznak a bővítményt tartalmazó aláírásfájlok formázása .fsi
.
Típusok esetén előnyben részesítse az általános (Foo<T>
) előtagszintaxisát néhány konkrét kivétellel
Az F# lehetővé teszi az általános típusok írásának postfix stílusát (például int list
) és az előtagstílust (például list<int>
).
A postfix stílus csak egyetlen típusargumentummal használható.
Mindig a .NET stílust részesíti előnyben, kivéve öt konkrét típust:
- F#-listák esetén használja a postfix űrlapot ahelyett,
int list
hogylist<int>
. - Az F# beállításainál használja a postfix űrlapot ahelyett,
int option
hogyoption<int>
. - Az F# értékbeállításaihoz használja a postfix űrlapot ahelyett,
int voption
hogyvoption<int>
a következőt használjuk. - F#-tömbök esetén használja a postfix űrlapot:
int array
helyett vagyint[]
helyettarray<int>
. - Hivatkozáscellák esetén használja
int ref
ref<int>
ahelyett vagyRef<int>
.
Minden más típushoz használja az előtag űrlapot.
Formázási függvénytípusok
Egy függvény aláírásának meghatározásakor használjon fehér szóközt a ->
szimbólum körül:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Formázási érték és argumentumtípus-széljegyzetek
Ha típusjegyzetekkel definiál értékeket vagy argumentumokat, használjon üres szóközt a :
szimbólum után, de előtte ne:
// ✔️ 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
Többsoros típusú széljegyzetek formázása
Ha egy szöveg széljegyzete hosszú vagy többsoros, helyezze őket a következő sorba, egy szinttel behúzva.
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
Beágyazott névtelen rekordtípusok esetén a stílust is használhatja Stroustrup
:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Formázás– visszatérési típusú széljegyzetek
A függvényben vagy tag típusjegyzetekben használjon üres szóközt a :
szimbólum előtt és után:
// ✔️ 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
Formázási típusok az aláírásokban
Ha teljes függvénytípusokat ír aláírásokban, néha szükség van az argumentumok több sorra való felosztására. A visszatérési típus mindig be van húzva.
A tupled függvények argumentumait az egyes sorok végén elhelyezett argumentumok választják el *
egymástól.
Vegyünk például egy függvényt a következő implementációval:
let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
A megfelelő aláírásfájlban (.fsi
kiterjesztésben) a függvény a következőképpen formázható, ha többsoros formázásra van szükség:
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list
Hasonlóképpen fontolja meg a curried függvényt is:
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
A megfelelő aláírási fájlban a ->
sorokat az egyes sorok végén helyezik el:
// ✔️ OK
val SampleCurriedFunction:
arg1: string ->
arg2: string ->
arg3: int ->
arg4: int ->
int list
Hasonlóképpen fontolja meg egy olyan függvényt, amely a curried és a tupled argumentumok kombinációját használja:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
A megfelelő aláírási fájlban a tuple előtt lévő típusok be vannak húzva
// ✔️ 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
Ugyanezek a szabályok vonatkoznak a típusaadékokban lévő tagokra is:
type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string
Explicit általános típusú argumentumok és megkötések formázása
Az alábbi irányelvek a függvénydefiníciókra, tagdefiníciókra, típusdefiníciókra és függvényalkalmazásokra vonatkoznak.
Ha nem túl hosszú, tartsa meg az általános típusargumentumokat és korlátozásokat egy sorban:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Ha az általános típusargumentumok/kényszerek és függvényparaméterek nem felelnek meg, de a típusparaméterek/kényszerek önmagukban igen, helyezze a paramétereket az új sorokra:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
param
=
// function body
Ha a típusparaméterek vagy kényszerek túl hosszúak, bontsa meg és igazítsa őket az alább látható módon. A típusparaméterek listájának megőrzése a függvényével megegyező sorban, a hosszától függetlenül. Korlátozások esetén helyezze when
az első sorra, és tartsa az egyes kényszereket egyetlen sorban, függetlenül annak hosszától. Helyezze >
az utolsó sor végére. A kényszerek behúzása egy szinttel.
// ✔️ 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
Ha a típusparaméterek/megkötések fel vannak bontva, de nincsenek normál függvényparaméterek, helyezze az =
új sorra, függetlenül attól, hogy:
// ✔️ 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
A függvényalkalmazásokra ugyanazok a szabályok vonatkoznak:
// ✔️ 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
Formázás öröklése
Az alaposztály-konstruktor argumentumai megjelennek a záradék argumentumlistájában inherit
.
Helyezze a inherit
záradékot egy új sorra, egy szinttel behúzva.
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)
Ha a konstruktor hosszú vagy többsoros, helyezze őket a következő sorba, egy szinttel behúzva.
Formázza ezt a többsoros konstruktort a többsoros függvényalkalmazások szabályai szerint.
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
""")
Az elsődleges konstruktor formázása
Az alapértelmezett formázási konvenciókban nincs hely a típusnév és az elsődleges konstruktor zárójelei között.
// ✔️ 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
Több konstruktor
Ha a inherit
záradék egy rekord része, helyezze ugyanazon a sorban, ha rövid.
És tegye a következő sorba, egy szinttel behúzva, ha hosszú vagy többsoros.
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 }
Formázási attribútumok
Az attribútumok egy szerkezet fölé kerülnek:
// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...
// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x
// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
Minden XML-dokumentáció után kell menniük:
// ✔️ OK
/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x
Paraméterek formázási attribútumai
Az attribútumok paraméterekre is elhelyezhetők. Ebben az esetben helyezze a paraméter és a név elé a következő sort:
// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)
Több attribútum formázása
Ha több attribútumot alkalmaz egy olyan szerkezetre, amely nem paraméter, helyezze az egyes attribútumokat egy külön sorba:
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
Ha paraméterre van alkalmazva, helyezze az attribútumokat ugyanarra a sorra, és különítse el őket egy ;
elválasztóval.
Köszönetnyilvánítás
Ezek az irányelvek Anh-Dung Phan F#-formázási konvenciókra vonatkozó átfogó útmutatóján alapulnak.