Panduan pemformatan kode F#
Artikel ini menawarkan panduan tentang cara memformat kode sehingga kode F# Anda:
- Lebih mudah terbaca
- Sesuai dengan konvensi yang diterapkan oleh alat pemformatan di Visual Studio Code dan editor lainnya
- Mirip dengan kode online lainnya
Lihat juga Konvensi pengkodean dan Pedoman desain komponen, yang juga mencakup konvensi penamaan.
Pemformatan kode otomatis
Pemformat kode Fantomas adalah alat standar komunitas F# untuk pemformatan kode otomatis. Pengaturan default sesuai dengan panduan gaya ini.
Kami sangat merekomendasikan penggunaan pemformat kode ini. Dalam tim F#, spesifikasi pemformatan kode harus disepakati dan dikodifikasi dalam hal file pengaturan yang disepakati untuk pemformat kode yang diperiksa ke repositori tim.
Aturan umum untuk pemformatan
F# menggunakan ruang kosong yang signifikan secara default dan sensitif terhadap ruang kosong. Panduan berikut dimaksudkan untuk memberikan panduan tentang cara mengatasi beberapa tantangan yang muncul.
Gunakan spasi bukan tab
Saat indentasi diperlukan, Anda harus menggunakan spasi, bukan tab. Kode F# tidak menggunakan tab, dan pengkompilasi akan memberikan kesalahan jika karakter tab ditemui di luar string literal atau komentar.
Gunakan indentasi yang konsisten
Saat mengindentasi, setidaknya diperlukan satu spasi. Organisasi Anda dapat membuat standar pembuatan kode untuk menentukan jumlah spasi yang akan digunakan untuk indentasi; dua, tiga, atau empat spasi indentasi di setiap tingkat di tempat kemunculan indentasi adalah tipikal.
Kami merekomendasikan empat spasi per indentasi.
Dengan kata lain, indentasi program adalah masalah subjektif. Variasinya tidak apa-apa, tetapi aturan pertama yang harus Anda ikuti adalah konsistensi indentasi. Pilih gaya indentasi yang diterima secara umum dan gunakan secara sistematis di seluruh basis kode Anda.
Hindari pemformatan yang sensitif terhadap panjang nama
Cobalah menghindari indentasi dan perataan yang sensitif terhadap penamaan:
// ✔️ 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 _ -> ()
| ...
Alasan utama untuk menghindarinya adalah:
- Kode penting dipindahkan jauh ke kanan
- Ada lebih sedikit lebar yang tersisa untuk kode aktual
- Mengganti nama dapat merusak perataan
Hindari ruang kosong asing
Hindari ruang kosong asing dalam kode F#, kecuali jika dijelaskan dalam panduan gaya ini.
// ✔️ OK
spam (ham 1)
// ❌ Not OK
spam ( ham 1 )
Memformat komentar
Utamakan beberapa komentar garis miring ganda daripada komentar blok.
// 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.
*)
Komentar harus memanfaatkan huruf pertama dan menjadi frasa atau kalimat yang terbentuk dengan baik.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Untuk memformat komentar dokumen XML, lihat "Deklarasi pemformatan" di bawah ini.
Ekspresi pemformatan
Bagian ini membahas ekspresi pemformatan dari berbagai jenis.
Memformat ekspresi string
String literal dan string terinterpolasi hanya dapat dibiarkan pada satu baris, terlepas dari berapa lama garisnya.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
Ekspresi terinterpolasi multibaris tidak disarankan. Sebagai gantinya, ikat hasil ekspresi ke nilai dan gunakan dalam string terinterpolasi.
Memformat ekspresi tuple
Instansiasi tuple harus menggunakan tanda kurung, dan koma pembatas di dalamnya harus diikuti oleh satu spasi, misalnya: (1, 2)
, (x, y, z)
.
// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]
Biasanya diterima untuk menghilangkan tanda kurung dalam pencocokan pola tuple:
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
Biasanya juga diterima untuk menghilangkan tanda kurung jika tuple adalah nilai pengembalian fungsi:
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
Singkatnya, utamakan instantiasi tuple yang menggunakan tanda kurung, tetapi saat menggunakan tuple untuk pencocokan pola atau nilai pengembalian, sah-sah saja menghindari tanda kurung.
Memformat ekspresi aplikasi
Saat memformat aplikasi fungsi atau metode, argumen disediakan pada baris yang sama ketika lebar garis memungkinkan:
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Hilangkan tanda kurung kecuali argumen mengharuskannya:
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)
// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)
Jangan hilangkan spasi saat memanggil dengan beberapa argumen kurir:
// ✔️ 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)
Dalam konvensi pemformatan default, spasi ditambahkan saat menerapkan fungsi huruf kecil ke argumen tupled atau bertanda kurung (bahkan ketika argumen tunggal digunakan):
// ✔️ 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)
Dalam konvensi pemformatan default, tidak ada spasi yang ditambahkan saat menerapkan metode berkapitalisasi ke argumen tupled. Ini karena ini sering digunakan dengan pemrograman yang fasih:
// ✔️ 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)
Anda mungkin perlu meneruskan argumen ke fungsi pada baris baru sebagai masalah keterbacaan atau karena daftar argumen atau nama argumen terlalu panjang. Dalam hal ini, inden satu tingkat:
// ✔️ 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)
Ketika fungsi mengambil argumen tuple multibaris tunggal, tempatkan setiap argumen pada baris baru:
// ✔️ 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)
Jika ekspresi argumen pendek, pisahkan argumen dengan spasi dan simpan dalam satu baris.
// ✔️ OK
let person = new Person(a1, a2)
// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)
// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)
Jika ekspresi argumen panjang, gunakan baris baru dan inden satu tingkat, daripada indentasi ke tanda kurung kiri.
// ✔️ 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)
Aturan yang sama berlaku bahkan jika hanya ada argumen multibaris tunggal, termasuk string multibaris:
// ✔️ 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" ]
)
Memformat ekspresi alur
Saat menggunakan beberapa baris, operator alur |>
harus berada di bawah ekspresi tempat mereka beroperasi.
// ✔️ 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
Menggunakan Ekspresi Lambda
Saat ekspresi lambda digunakan sebagai argumen dalam ekspresi multibaris, dan diikuti oleh argumen lain, tempatkan isi ekspresi lambda pada baris baru, diindentasi satu tingkat:
// ✔️ OK
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")
list1
Jika argumen lambda adalah argumen terakhir dalam aplikasi fungsi, tempatkan semua argumen hingga panah pada baris yang sama.
// ✔️ 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}")
Perlakukan lambda yang cocok dengan cara yang sama.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Ketika ada banyak argumen terdepan atau multibaris sebelum lambda mengindentasi semua argumen dengan satu tingkat.
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Jika isi ekspresi lambda panjangnya beberapa baris, Anda harus mempertimbangkan untuk merefaktorkannya ke dalam fungsi yang dicakup secara lokal.
Saat alur menyertakan ekspresi lambda, setiap ekspresi lambda biasanya merupakan argumen terakhir di setiap tahap alur:
// ✔️ 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}")
Jika argumen lambda tidak pas pada satu baris, atau multibaris sendiri, letakkan di baris berikutnya, diindentasi oleh satu tingkat.
// ✔️ 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 ()
Memformat ekspresi aritmetika dan biner
Selalu gunakan ruang kosong di sekitar ekspresi aritmetika biner:
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
Gagal mengelilingi operator biner -
, ketika dikombinasikan dengan pilihan pemformatan tertentu, dapat menyebabkan interpretasi sebagai unary -
.
Operator unary -
harus selalu diikuti oleh nilai yang dinegasikan:
// ✔️ OK
let negate x = -x
// ❌ Not OK
let negateBad x = - x
Menambahkan karakter ruang kosong setelah operator -
dapat menyebabkan kebingungan bagi orang lain.
Pisahkan operator biner menurut spasi. Ekspresi infiks boleh ditempatkan pada kolom yang sama:
// ✔️ OK
let function1 () =
acc +
(someFunction
x.IngredientName x.Quantity)
// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Aturan ini juga berlaku untuk satuan ukuran dalam jenis dan anotasi konstanta:
// ✔️ 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)> }
Operator berikut didefinisikan dalam pustaka standar F# dan harus digunakan alih-alih mendefinisikan yang setara. Menggunakan operator ini disarankan karena cenderung membuat kode lebih mudah dibaca dan idiomatis. Daftar berikut ini meringkas operator F# yang direkomendasikan.
// ✔️ 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
Memformat ekspresi operator rentang
Hanya tambahkan spasi di sekitar ..
saat semua ekspresi adalah non-atomik.
Bilangan bulat dan pengidentifikasi kata tunggal dianggap atomik.
// ✔️ 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 ]
Aturan ini juga berlaku untuk memotong:
// ✔️ OK
arr[0..10]
list[..^1]
Pemformatan jika ekspresi
Indentasi kondisional tergantung pada ukuran dan kompleksitas ekspresi yang membentuknya. Tulis dalam satu baris ketika:
cond
,e1
, dane2
pendek.e1
dane2
bukanif/then/else
merupakan ekspresi diri mereka sendiri.
// ✔️ OK
if cond then e1 else e2
Jika ekspresi lain tidak ada, disarankan untuk tidak pernah menulis seluruh ekspresi dalam satu baris. Ini untuk membedakan kode imperatif dari fungsional.
// ✔️ OK
if a then
()
// ❌ Not OK, code formatters will reformat to the above by default
if a then ()
Jika salah satu ekspresi adalah multibaris, setiap cabang bersyarkat harus multibaris.
// ✔️ OK
if cond then
let e1 = something()
e1
else
e2
// ❌ Not OK
if cond then
let e1 = something()
e1
else e2
Beberapa kondisional dengan elif
dan else
diindentasi pada cakupan yang sama dengan if
saat mereka mengikuti aturan ekspresi if/then/else
satu baris.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Jika salah satu kondisi atau ekspresi adalah multibaris, seluruh ekspresi if/then/else
adalah multibaris:
// ✔️ 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
Jika kondisi multibaris atau melebihi toleransi default baris tunggal, ekspresi kondisi harus menggunakan satu indentasi dan baris baru.
Kata if
kunci dan then
harus selaras saat merangkum ekspresi kondisi panjang.
// ✔️ 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
Namun, ini adalah gaya yang lebih baik untuk merefaktor kondisi panjang ke let binding atau fungsi terpisah:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
if performAction then
e1
else
e2
Memformat ekspresi kasus union
Menerapkan kasus union yang didiskriminasi mengikuti aturan yang sama dengan aplikasi fungsi dan metode. Artinya, karena namanya dikapitalisasi, pemformat kode akan menghapus spasi sebelum tuple:
// ✔️ OK
let opt = Some("A", 1)
// OK, but code formatters will remove the space
let opt = Some ("A", 1)
Seperti aplikasi fungsi, konstruksi yang dibagi di beberapa baris harus menggunakan indentasi:
// ✔️ OK
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
Memformat daftar dan ekspresi array
Tulis x :: l
dengan ruang di sekitar operator ::
(::
adalah operator infiks, karenanya dikelilingi oleh spasi).
Daftar dan array yang dideklarasikan pada satu baris harus memiliki spasi setelah tanda kurung siku pembuka dan sebelum kurung tutup:
// ✔️ OK
let xs = [ 1; 2; 3 ]
// ✔️ OK
let ys = [| 1; 2; 3; |]
Selalu gunakan setidaknya satu spasi antara dua operator seperti kurung kurawal yang berbeda. Misalnya, biarkan spasi antara [
dan {
.
// ✔️ 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 }]
Pedoman yang sama berlaku untuk daftar atau array tuple.
Daftar dan array yang dibagi di beberapa baris mengikuti aturan serupa seperti yang dilakukan rekaman:
// ✔️ 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 |] |]
Seperti halnya rekaman, mendeklarasikan tanda kurung buka dan tutup pada baris mereka sendiri akan membuat memindahkan kode di sekitar dan mempipa ke dalam fungsi lebih mudah:
// ✔️ 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 |]
|]
Jika ekspresi daftar atau array adalah sisi kanan pengikatan, Anda mungkin lebih suka menggunakan Stroustrup
gaya:
// ✔️ 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 |]
|]
Namun, ketika ekspresi daftar atau array bukan sisi kanan pengikatan, seperti ketika berada di dalam daftar atau array lain, jika ekspresi dalam tersebut perlu mencakup beberapa baris, tanda kurung harus pergi pada baris mereka sendiri:
// ✔️ 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
] ]
Aturan yang sama berlaku untuk jenis rekaman di dalam array/daftar:
// ✔️ 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
} ]
Saat membuat array dan daftar secara terprogram, utamakan ->
daripada do ... yield
ketika nilai selalu dihasilkan:
// ✔️ 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 ]
Versi F# yang lebih lama perlu menentukan yield
dalam situasi ketika data dapat dihasilkan secara kondisional, atau mungkin ada ekspresi berturut-turut yang akan dievaluasi. Utamakan menghilangkan kata kunci yield
ini kecuali Anda harus mengompilasi dengan versi bahasa F# yang lebih lama:
// ✔️ 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"
]
Dalam beberapa kasus, do...yield
dapat membantu keterbacaan. Kasus-kasus ini, meskipun subjektif, harus dipertimbangkan.
Memformat ekspresi rekaman
Rekaman pendek dapat ditulis dalam satu baris:
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
Rekaman yang lebih panjang harus menggunakan baris baru untuk label:
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Gaya pemformatan tanda kurung siku multibaris
Untuk rekaman yang mencakup beberapa baris, ada tiga gaya pemformatan yang umum digunakan: Cramped
, , Aligned
dan Stroustrup
. Gaya Cramped
telah menjadi gaya default untuk kode F#, karena cenderung mendukung gaya yang memungkinkan pengkompilasi untuk mengurai kode dengan mudah. Kedua Aligned
gaya dan Stroustrup
memungkinkan penyusunan ulang anggota yang lebih mudah, yang mengarah ke kode yang mungkin lebih mudah untuk direfaktor, dengan kelemahan bahwa situasi tertentu mungkin memerlukan sedikit lebih banyak kode verbose.
Cramped
: Format rekaman standar historis, dan default F#. Kurung siku pembuka berjalan pada baris yang sama dengan anggota pertama, menutup kurung siku pada baris yang sama dengan anggota terakhir.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Aligned
: Tanda kurung masing-masing mendapatkan barisnya sendiri, diratakan pada kolom yang sama.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }
Stroustrup
: Kurung siku pembuka berjalan pada baris yang sama dengan pengikatan, kurung siku penutup mendapatkan garisnya sendiri.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Aturan gaya pemformatan yang sama berlaku untuk elemen daftar dan array.
Ekspresi Catatan Salin dan Perbarui
Ekspresi rekaman salin dan perbarui masih merupakan rekaman, sehingga panduan serupa berlaku.
Ekspresi pendek dapat ditempatkan dalam satu baris:
// ✔️ OK
let point2 = { point with X = 1; Y = 2 }
Ekspresi yang lebih panjang harus menggunakan baris baru, dan format berdasarkan salah satu konvensi bernama di atas:
// ✔️ 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 = ""
}
}
Catatan: Jika menggunakan Stroustrup
gaya untuk ekspresi salin dan perbarui, Anda harus mengindentasi anggota lebih jauh dari nama rekaman yang disalin:
// ✔️ 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"
}
Pencocokan pola pemformatan
Gunakan |
untuk setiap klausa kecocokan tanpa indentasi. Jika ekspresi pendek, Anda dapat mempertimbangkan untuk menggunakan satu baris jika setiap subekspresi juga sederhana.
// ✔️ 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"
Jika ekspresi di sebelah kanan panah pencocokan pola terlalu besar, pindahkan ke baris berikut, diindentasi satu langkah dari match
/|
.
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Mirip dengan kondisi jika besar, jika ekspresi kecocokan multibaris atau melebihi toleransi default baris tunggal, ekspresi kecocokan harus menggunakan satu indentasi dan baris baru.
Kata match
kunci dan with
harus selaras saat merangkum ekspresi kecocokan panjang.
// ✔️ OK, but better to refactor, see below
match
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
with
| X y -> y
| _ -> 0
Namun, ini adalah gaya yang lebih baik untuk merefaktor ekspresi kecocokan panjang dengan fungsi biarkan mengikat atau terpisah:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
match performAction with
| X y -> y
| _ -> 0
Menyelaraskan panah dari kecocokan pola harus dihindari.
// ✔️ 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
Pencocokan pola yang diperkenalkan dengan menggunakan kata kunci function
harus mengindentasi satu tingkat dari awal baris sebelumnya:
// ✔️ OK
lambdaList
|> List.map (function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
Penggunaan function
dalam fungsi yang ditentukan oleh let
atau let rec
secara umum harus dihindari demi match
. Jika digunakan, aturan pola harus selaras dengan kata kunci function
:
// ✔️ OK
let rec sizeLambda acc =
function
| Abs(x, body) -> sizeLambda (succ acc) body
| App(lam1, lam2) -> sizeLambda (sizeLambda acc lam1) lam2
| Var v -> succ acc
Memformat ekspresi coba/dengan
Pencocokan pola pada jenis pengecualian harus diindentasi pada tingkat yang sama dengan with
.
// ✔️ OK
try
if System.DateTime.Now.Second % 3 = 0 then
raise (new System.Exception())
else
raise (new System.ApplicationException())
with
| :? System.ApplicationException ->
printfn "A second that was not a multiple of 3"
| _ ->
printfn "A second that was a multiple of 3"
Tambahkan |
untuk setiap klausa, kecuali jika hanya ada satu klausa:
// ✔️ 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
Memformat argumen bernama
Argumen bernama harus memiliki spasi di sekitar =
:
// ✔️ 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)
Saat pencocokan pola menggunakan union yang didiskriminasi, pola bernama diformat sama, misalnya.
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
Memformat ekspresi mutasi
Ekspresi mutasi location <- expr
biasanya diformat pada satu baris.
Jika memerlukan pemformatan multibaris, tempatkan ekspresi sisi kanan pada baris baru.
// ✔️ 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
Memformat ekspresi objek
Anggota ekspresi objek harus sejajar dengan member
yang diindentasi setinggi satu tingkat.
// ✔️ 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) }
Anda mungkin juga lebih suka menggunakan Stroustrup
gaya:
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)
}
Definisi jenis kosong dapat diformat pada satu baris:
type AnEmptyType = class end
Terlepas dari lebar halaman yang dipilih, = class end
harus selalu berada di baris yang sama.
Memformat ekspresi indeks/potong
Ekspresi indeks tidak boleh berisi spasi apa pun di sekitar tanda kurung buka dan tutup.
// ✔️ OK
let v = expr[idx]
let y = myList[0..1]
// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]
Ini juga berlaku untuk sintaksis expr.[idx]
yang lebih lama.
// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]
// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]
Memformat ekspresi yang dikutip
Simbol pemisah (<@
, , @>
, <@@
@@>
) harus ditempatkan pada baris terpisah jika ekspresi yang dikutip adalah ekspresi multibaris.
// ✔️ OK
<@
let f x = x + 10
f 20
@>
// ❌ Not OK
<@ let f x = x + 10
f 20
@>
Dalam ekspresi baris tunggal, simbol pemisah harus ditempatkan pada baris yang sama dengan ekspresi itu sendiri.
// ✔️ OK
<@ 1 + 1 @>
// ❌ Not OK
<@
1 + 1
@>
Memformat ekspresi berantai
Ketika ekspresi berantai (aplikasi fungsi yang terjalin dengan .
) panjang, letakkan setiap pemanggilan aplikasi di baris berikutnya.
Inden tautan berikutnya dalam rantai satu tingkat setelah tautan di awal.
// ✔️ OK
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
// ✔️ OK
Cli
.Wrap("git")
.WithArguments(arguments)
.WithWorkingDirectory(__SOURCE_DIRECTORY__)
.ExecuteBufferedAsync()
.Task
Tautan terkemuka dapat terdiri dari beberapa tautan jika merupakan pengidentifikasi sederhana. Misalnya, penambahan namespace yang sepenuhnya memenuhi syarat.
// ✔️ OK
Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
Tautan berikutnya juga harus berisi pengidentifikasi sederhana.
// ✔️ 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))
Saat argumen di dalam aplikasi fungsi tidak pas pada baris lainnya, letakkan setiap argumen di baris berikutnya.
// ✔️ 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
Argumen Lambda di dalam aplikasi fungsi harus dimulai pada baris yang sama dengan pembukaan (
.
// ✔️ OK
builder
.WithEnvironment()
.WithLogger(fun loggerConfiguration ->
// ...
())
// ❌ Not OK, formatting tools will reformat to the above
builder
.WithEnvironment()
.WithLogger(
fun loggerConfiguration ->
// ...
())
Memformat deklarasi
Bagian ini membahas ekspresi pemformatan dari berbagai jenis.
Menambahkan baris kosong di antara deklarasi
Pisahkan fungsi tingkat atas dan definisi kelas dengan satu baris kosong. Contohnya:
// ✔️ 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
Jika konstruksi memiliki komentar dokumen XML, tambahkan baris kosong sebelum komentar.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Memformat let dan deklarasi anggota
Saat pemformatan let
dan member
deklarasi, biasanya sisi kanan pengikatan berjalan pada satu baris, atau (jika terlalu panjang) berjalan pada baris baru yang diindentasikan satu tingkat.
Misalnya, contoh berikut sesuai:
// ✔️ 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
Ini tidak sesuai:
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
let d = while f do
printfn "%A" x
Instansiasi jenis rekaman juga dapat menempatkan tanda kurung pada baris mereka sendiri:
// ✔️ OK
let bilbo =
{
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Anda mungkin juga lebih suka menggunakan Stroustrup
gaya, dengan pembukaan {
pada baris yang sama dengan nama pengikatan:
// ✔️ OK
let bilbo = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Pisahkan anggota dengan satu baris kosong dan dokumen serta tambahkan komentar dokumentasi:
// ✔️ OK
/// This is a thing
type ThisThing(value: int) =
/// Gets the value
member _.Value = value
/// Returns twice the value
member _.TwiceValue() = value*2
Baris kosong ekstra dapat digunakan (dengan hemat) untuk memisahkan grup fungsi terkait. Baris kosong dapat dihilangkan di antara sekelompok liner satu terkait (misalnya, satu set implementasi dummy). Gunakan baris kosong dalam fungsi, dengan hemat, untuk menunjukkan bagian logis.
Memformat fungsi dan argumen anggota
Saat menentukan fungsi, gunakan ruang kosong di sekitar setiap argumen.
// ✔️ 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
Jika Anda memiliki definisi fungsi yang panjang, tempatkan parameter pada baris baru dan inden agar sesuai dengan tingkat indentasi parameter berikutnya.
// ✔️ 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
Ini juga berlaku untuk anggota, konstruktor, dan parameter yang menggunakan tuple:
// ✔️ 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
Jika parameter menggunakan curried, tempatkan karakter =
bersama dengan jenis pengembalian apa pun pada baris baru:
// ✔️ 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
Ini adalah cara untuk menghindari garis yang terlalu panjang (jika jenis pengembalian mungkin memiliki nama panjang) dan memiliki lebih sedikit kerusakan baris saat menambahkan parameter.
Memformat deklarasi operator
Secara opsional gunakan ruang kosong untuk mengelilingi definisi operator:
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
Untuk operator kustom apa pun yang dimulai dengan *
dan yang memiliki lebih dari satu karakter, Anda perlu menambahkan spasi kosong ke awal definisi untuk menghindari ambiguitas kompilator. Karena itu, kami sarankan Anda hanya mengelilingi definisi semua operator dengan satu karakter ruang kosong.
Memformat deklarasi rekaman
Untuk deklarasi rekaman, secara default Anda harus mengindentasi {
dalam definisi jenis dengan empat spasi, memulai daftar label pada baris yang sama, dan meratakan anggota, jika ada, dengan {
token:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
Juga umum untuk lebih memilih menempatkan tanda kurung pada baris mereka sendiri, dengan label yang diindentasikan oleh empat spasi tambahan:
// ✔️ OK
type PostalAddress =
{
Address: string
City: string
Zip: string
}
Anda juga dapat meletakkan {
di akhir baris pertama dari definisi jenis (Stroustrup
gaya):
// ✔️ OK
type PostalAddress = {
Address: string
City: string
Zip: string
}
Jika anggota tambahan diperlukan, jangan gunakan with
/end
jika memungkinkan:
// ✔️ 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
Pengecualian untuk aturan gaya ini adalah jika Anda memformat rekaman sesuai dengan Stroustrup
gaya. Dalam situasi ini, karena aturan pengkompilasi, with
kata kunci diperlukan jika Anda ingin menerapkan antarmuka atau menambahkan anggota tambahan:
// ✔️ 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}"
Saat dokumentasi XML ditambahkan untuk bidang rekaman, Aligned
atau Stroustrup
gaya lebih disukai, dan spasi kosong tambahan harus ditambahkan di antara anggota:
// ❌ 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}"
Menempatkan token pembuka pada baris baru dan token penutup pada baris baru lebih disukai jika Anda mendeklarasikan implementasi antarmuka atau anggota pada rekaman:
// ✔️ 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
Aturan yang sama ini berlaku untuk alias jenis rekaman anonim.
Memformat deklarasi gabungan yang didiskriminasi
Untuk deklarasi union terdiskriminasi, inden |
dalam definisi jenis sebanyak empat spasi:
// ✔️ 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
Ketika ada satu union pendek, Anda dapat menghilangkan |
yang tedepan.
// ✔️ OK
type Address = Address of string
Untuk union yang lebih panjang atau multibaris, simpan dan tempatkan |
setiap bidang union pada baris baru, dengan pemisahan *
di akhir setiap baris.
// ✔️ 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
Saat komentar dokumentasi ditambahkan, gunakan baris kosong sebelum setiap komentar ///
.
// ✔️ 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
Memformat deklarasi literal
Literal F# yang menggunakan atribut Literal
harus menempatkan atribut pada barisnya sendiri dan menggunakan penamaan PascalCase:
// ✔️ OK
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Hindari menempatkan atribut pada baris yang sama dengan nilai.
Memformat deklarasi modul
Kode dalam modul lokal harus diindentasi relatif terhadap modul, tetapi kode dalam modul tingkat atas tidak boleh diindentasi. Elemen namespace tidak harus diindentasi.
// ✔️ 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
Deklarasi pemformatan
Dalam deklarasi jenis, deklarasi modul dan ekspresi komputasi, penggunaan do
atau do!
terkadang diperlukan untuk memberikan efek samping pada operasi.
Ketika ini mencakup beberapa baris, gunakan indentasi dan baris baru untuk menjaga indentasi tetap konsisten dengan let
/let!
. Berikut adalah contoh penggunaan do
di kelas:
// ✔️ 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
Berikut adalah contoh dengan do!
menggunakan dua spasi indentasi (karena dengan do!
kebetulan tidak ada perbedaan antara pendekatan saat menggunakan empat spasi indentasi):
// ✔️ 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
}
Memformat operasi ekspresi komputasi
Saat membuat operasi kustom untuk ekspresi komputasi, disarankan untuk menggunakan penamaan camelCase:
// ✔️ 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
}
Domain yang sedang dimodelkan pada akhirnya harus mendorong konvensi penamaan. Jika idiomatik untuk menggunakan konvensi yang berbeda, konvensi tersebut harus digunakan sebagai gantinya.
Jika nilai pengembalian ekspresi adalah ekspresi komputasi, lebih suka menempatkan nama kata kunci ekspresi komputasi pada barisnya sendiri:
// ✔️ OK
let foo () =
async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Anda mungkin juga lebih suka menempatkan ekspresi komputasi pada baris yang sama dengan nama pengikatan:
// ✔️ OK
let foo () = async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Mana pun preferensi Anda, Anda harus bertujuan untuk tetap konsisten di seluruh basis kode Anda. Pemformat mungkin memungkinkan Anda menentukan preferensi ini agar tetap konsisten.
Memformat tipe dan anotasi jenis
Bagian ini membahas jenis pemformatan dan anotasi jenis. Ini termasuk memformat file tanda tangan dengan ekstensi .fsi
.
Untuk jenis, utamakan sintaksis awalan untuk generik (Foo<T>
), dengan beberapa pengecualian tertentu
F# memungkinkan gaya postfiks menulis jenis generik (misalnya, int list
) dan gaya awalan (misalnya, list<int>
).
Gaya postfiks hanya dapat digunakan dengan argumen tipe tunggal.
Selalu pilih gaya .NET, kecuali untuk lima jenis tertentu:
- Untuk Daftar F#, gunakan jenis postfix:
int list
bukanlist<int>
. - Untuk Opsi F#, gunakan jenis postfix:
int option
bukanoption<int>
. - Untuk Opsi Nilai F#, gunakan jenis postfix:
int voption
bukanvoption<int>
. - Untuk array F#, gunakan formulir postfix:
int array
bukanarray<int>
atauint[]
. - Untuk Sel Referensi, gunakan
int ref
bukanref<int>
atauRef<int>
.
Untuk semua jenis lainnya, gunakan formulir awalan.
Memformat tipe fungsi
Saat menentukan tanda tangan fungsi, gunakan spasi kosong di sekitar simbol ->
:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Memformat nilai dan anotasi jenis argumen
Saat menentukan nilai atau argumen dengan anotasi jenis, gunakan spasi kosong setelah simbol :
, tetapi tidak sebelum:
// ✔️ 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
Memformat anotasi tipe multibaris
Ketika anotasi jenis panjang atau multibaris, letakkan pada baris berikutnya, diindentasi oleh satu tingkat.
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
Untuk jenis catatan anonim sebaris, Anda juga dapat menggunakan Stroustrup
gaya:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Memformat anotasi tipe pengembalian
Dalam fungsi atau anggota mengembalikan anotasi jenis, gunakan spasi kosong sebelum dan sesudah simbol :
:
// ✔️ 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
Memformat tipe dalam tanda tangan
Saat menulis jenis fungsi penuh dalam tanda tangan, terkadang perlu untuk membagi argumen melalui beberapa baris. Jenis pengembalian selalu diindentasi.
Untuk fungsi tuple, argumen dipisahkan oleh *
, ditempatkan di akhir setiap baris.
Misalnya, pertimbangkan fungsi dengan implementasi berikut:
let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
Dalam file tanda tangan (ekstensi .fsi
) yang sesuai, fungsi dapat diformat sebagai berikut saat pemformatan multibaris diperlukan:
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list
Demikian juga pertimbangkan fungsi curried:
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
Dalam file tanda tangan yang sesuai, ->
ditempatkan di akhir setiap baris:
// ✔️ OK
val SampleCurriedFunction:
arg1: string ->
arg2: string ->
arg3: int ->
arg4: int ->
int list
Demikian juga, pertimbangkan fungsi yang mengambil campuran argumen curried dan tuple:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
Dalam file tanda tangan yang sesuai, jenis yang didahului oleh tuple diinden
// ✔️ 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
Aturan yang sama berlaku untuk anggota dalam jenis tanda tangan:
type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string
Memformat argumen dan batasan tipe generik eksplisit
Panduan di bawah ini berlaku untuk definisi fungsi, definisi anggota, definisi jenis, dan aplikasi fungsi.
Pertahankan argumen dan batasan jenis generik pada satu baris jika tidak terlalu panjang:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Jika argumen/batasan jenis generik dan parameter fungsi tidak cocok, tetapi parameter/batasan jenis saja yang dilakukan, tempatkan parameter pada baris baru:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
param
=
// function body
Jika parameter atau batasan jenis terlalu panjang, pisahkan dan ratakan seperti yang ditunjukkan di bawah ini. Pertahankan daftar parameter jenis pada baris yang sama dengan fungsi, berapa pun panjangnya. Untuk batasan, tempatkan when
pada baris pertama, dan pertahankan setiap batasan pada satu baris berapa pun panjangnya. Tempatkan >
di akhir baris terakhir. Inden batasan sebanyak satu tingkat.
// ✔️ 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
Jika parameter/batasan jenis dipecah, tetapi tidak ada parameter fungsi normal, letakkan =
pada baris baru terlepas dari:
// ✔️ 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
Aturan yang sama berlaku untuk aplikasi fungsi:
// ✔️ 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
Pewarisan pemformatan
Argumen untuk konstruktor kelas dasar muncul dalam daftar argumen dalam klausa inherit
.
Letakkan inherit
klausa pada baris baru, diindentasi oleh satu tingkat.
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)
Ketika konstruktor panjang atau multibaris, letakkan di baris berikutnya, diindentasi oleh satu tingkat.
Format konstruktor multibaris ini sesuai dengan aturan aplikasi fungsi multibaris.
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
""")
Memformat konstruktor utama
Dalam konvensi pemformatan default, tidak ada spasi yang ditambahkan antara nama jenis dan tanda kurung untuk konstruktor utama.
// ✔️ 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
Beberapa konstruktor
inherit
Ketika klausa adalah bagian dari rekaman, letakkan di baris yang sama jika pendek.
Dan letakkan di baris berikutnya, diindentasi oleh satu tingkat, jika panjang atau multibaris.
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 }
Memformat atribut
Atribut ditempatkan di atas konstruksi:
// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...
// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x
// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
Hal-hal ini harus mengejar dokumentasi XML apa pun:
// ✔️ OK
/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x
Memformat atribut pada parameter
Atribut juga dapat ditempatkan pada parameter. Dalam hal ini, tempatkan kemudian pada baris yang sama dengan parameter dan sebelum nama:
// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)
Memformat beberapa atribut
Saat beberapa atribut diterapkan ke konstruksi yang bukan parameter, tempatkan setiap atribut pada baris terpisah:
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
Saat diterapkan ke parameter, tempatkan atribut pada baris yang sama dan pisahkan dengan pemisah ;
.
Ucapan terima kasih
Panduan ini didasarkan atas Panduan komprehensif untuk Konvensi Pemformatan F# oleh Anh-Dung Phan.