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, dan e2 pendek.
  • e1 dan e2 bukan if/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, , Aligneddan 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. Misalnya:

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

  1. Untuk Daftar F#, gunakan jenis postfix: int list bukan list<int>.
  2. Untuk Opsi F#, gunakan jenis postfix: int option bukan option<int>.
  3. Untuk Opsi Nilai F#, gunakan jenis postfix: int voption bukan voption<int>.
  4. Untuk array F#, gunakan formulir postfix: int array bukan array<int> atau int[].
  5. Untuk Sel Referensi, gunakan int ref bukan ref<int> atau Ref<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.