Bagikan melalui


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 bentuk file pengaturan yang sudah disepakati untuk pemformat kode yang dimasukkan ke dalam 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 pengkodean untuk menentukan jumlah spasi yang akan digunakan untuk indentasi; biasanya dua, tiga, atau empat spasi indentasi pada setiap tingkat yang memerlukan indentasi.

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
  • Lebar yang tersisa untuk kode nyata lebih sedikit.
  • Mengganti nama dapat merusak perataan

Hindari spasi kosong yang berlebihan

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

Prioritaskan penggunaan komentar garis miring ganda dibandingkan dengan 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 menggunakan huruf kapital pada huruf pertama dan dalam bentuk 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.

Pemformatan ekspresi string

Literal string dan string interpolasi dapat dituliskan dalam satu baris, tanpa memperhatikan seberapa panjangnya baris tersebut.

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

Umumnya diakui bahwa tanda kurung (parentheses) biasanya dihilangkan 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

Umumnya, diizinkan untuk menghilangkan tanda kurung saat tuple digunakan sebagai nilai balik dari sebuah 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 curried.

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

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

Konvensi pemformatan yang sama ini berlaku untuk pencocokan pola. Nilai gaya F# pemformatan konsisten:

// ✔️ OK - Consistent formatting for expressions and patterns
let result = Some(value)

match result with
| Some(x) -> x
| None -> 0

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 tunggal berupa tuple multibaris, diletakkan 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 yang mereka operasikan.

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

Untuk operator alur <| terbalik, simpan ekspresi pendek pada satu baris. Saat panjang garis memerlukan pembungkusan, tempatkan argumen pada baris baru dan ratakan secara konsisten:

// ✔️ OK - short expressions stay on one line
let result = someFunction <| arg1 <| arg2 <| arg3

// ✔️ OK - longer expressions can wrap when necessary
failwith
<| sprintf "A very long error message that exceeds reasonable line length: %s - additional details: %s"
    longVariableName
    anotherLongVariableName

// ✔️ OK - align continuation lines with the operator
let longResult =
    someVeryLongFunctionName
    <| firstVeryLongArgumentName
    <| secondVeryLongArgumentName
    <| thirdVeryLongArgumentName

// ❌ Not OK - unnecessary wrapping of short expressions
failwith <| sprintf "short: %s"
                    value

Memformat 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 awal atau multibaris sebelum lambda, indentasikan 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 memiliki beberapa baris, letakkan di baris berikutnya dengan satu tingkat indentasi.

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

Saat menulis ekspresi malas satu baris, simpan semuanya pada satu baris:

// ✔️ OK
let x = lazy (computeValue())

// ✔️ OK  
let y = lazy (a + b)

Untuk ekspresi malas multibaris, tempatkan tanda kurung pembuka pada baris lazy yang sama dengan kata kunci, dengan isi ekspresi diindentasikan satu tingkat dan tanda kurung penutup selaras dengan pembukaan:

// ✔️ OK
let v =
    lazy (
        // some code
        let x = computeExpensiveValue()
        let y = computeAnotherValue()
        x + y
    )

// ✔️ OK
let handler =
    lazy (
        let connection = openConnection()
        let data = fetchData connection
        processData data
    )

Ini mengikuti pola yang sama dengan aplikasi fungsi lain dengan argumen multibaris. Tanda kurung pembuka tetap dengan lazy, dan ekspresi diindentasi satu tingkat.

Memformat ekspresi aritmetika dan biner

Selalu gunakan ruang kosong di sekitar ekspresi aritmetika biner:

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

Kegagalan untuk mengelilingi operator biner -, ketika dikombinasikan dengan pilihan pemformatan tertentu, dapat menyebabkan interpretasi sebagai operator unari -. 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 dapat disusun di 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 <| x // Reverse pipeline
f >> g // Forward composition
x |> ignore // Discard away a value
x + y // Overloaded addition (including string concatenation)
x - y // Overloaded subtraction
x * y // Overloaded multiplication
x / y // Overloaded division
x % y // Overloaded modulus
x && y // Lazy/short-cut "and"
x || y // Lazy/short-cut "or"
x <<< y // Bitwise left shift
x >>> y // Bitwise right shift
x ||| y // Bitwise or, also for working with “flags” enumeration
x &&& y // Bitwise and, also for working with “flags” enumeration
x ^^^ y // Bitwise xor, also for working with “flags” enumeration

Pemformatan ekspresi operator rentang

Hanya tambahkan spasi sekitar .. jika ada ekspresi 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 Ekspresi 'If'

Indentasi kondisional tergantung pada ukuran dan kompleksitas ekspresi yang membentuknya. Tulis dalam satu baris jika:

  • cond, e1, dan e2 pendek.
  • e1 dan e2 bukan ekspresi if/then/else itu 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 percabangan kondisional 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 kunci if dan then harus selaras saat mengapit ekspresi kondisi yang 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 terdiskriminasi mengikuti aturan yang sama dengan penerapan 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 yang berbentuk seperti kurung 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 pada rekor:

// ✔️ 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 catatan, mendeklarasikan tanda kurung buka dan tutup pada barisnya sendiri akan memudahkan memindahkan kode dan mengalirkan ke dalam fungsi:

// ✔️ 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 tidak berada di sisi kanan sebuah pengikatan, misalnya ketika berada di dalam daftar atau array lain, jika ekspresi di dalam tersebut perlu mencakup beberapa baris, tanda kurung harus ditempatkan pada baris tersendiri.

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

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 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 gaya Aligned 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 kode yang sedikit lebih mendetail.

  • Cramped: Format rekaman standar historis, dan default F#. Kurung pembuka ditempatkan pada baris yang sama dengan anggota pertama, kurung penutup ditempatkan 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 berada pada baris yang sama dengan susunan, sedangkan 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.

Pemformatan ekspresi catatan salin-dan-perbarui

Ekspresi catatan salin dan perbarui tetap merupakan catatan, sehingga panduan yang serupa tetap 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 gaya Stroustrup untuk ekspresi salin dan perbarui, Anda harus mengindentasi anggota lebih jauh daripada 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"

Pemformatan pencocokan pola harus konsisten dengan pemformatan ekspresi. Jangan tambahkan spasi sebelum tanda kurung buka argumen pola:

// ✔️ OK
match x with
| Some(y) -> y
| None -> 0

// ✔️ OK
match data with
| Success(value) -> value
| Error(msg) -> failwith msg

// ❌ Not OK, pattern formatting should match expression formatting
match x with
| Some (y) -> y
| None -> 0

Namun, gunakan spasi antara argumen curried terpisah dalam pola, sama seperti dalam ekspresi:

// ✔️ OK - space between curried arguments
match x with
| Pattern arg (a, b) -> processValues arg a b

// ❌ Not OK - missing space between curried arguments
match x with
| Pattern arg(a, b) -> processValues arg a b

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 if yang besar, jika pernyataan ekspresi cocok berbentuk multibaris atau melebihi toleransi standar untuk satu baris, maka pernyataan tersebut harus menggunakan satu indentasi dan baris baru. Kata kunci match dan with harus selaras saat mengenkapsulasi ekspresi pencocokan 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

Pemformatan ekspresi try/with

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 dengan nama

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 discriminated unions, pola bernama diformat dengan cara yang 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

Pemformatan 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 terkait dengan .) panjang, letakkan setiap pemanggilan fungsi 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 selanjutnya 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 deklarasi 'let' dan variabel anggota

Saat memformat deklarasi let dan member, biasanya sisi kanan dari sebuah binding berada dalam satu baris, atau (jika terlalu panjang) berada pada baris baru yang dilengkapi indentasi 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 tersendiri:

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

Anda mungkin juga lebih suka menggunakan gaya Stroustrup, dengan tag pembuka { 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 baris satu yang berkaitan (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 dari 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 rekor, secara default Anda harus menjorokkan { dalam definisi jenis dengan empat spasi, memulai daftar label pada baris yang sama, dan menyelaraskan 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 kolom data, gaya Aligned atau Stroustrup lebih disukai, dan spasi kosong tambahan harus ditambahkan di antara elemen-elemen.

// ❌ 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 pemisah * 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

Pemformatan 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.

Pemformatan 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 operasi yang mengakibatkan efek samping. 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 menentukan konvensi penamaan. Apabila lebih sesuai untuk menggunakan konvensi yang berbeda, maka konvensi tersebut sebaiknya digunakan.

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.

Pemformatan jenis dan anotasi tipe

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 penggunaan gaya postfiks untuk menulis tipe generik (misalnya, int list) dan gaya awalan (misalnya, list<int>). Gaya postfiks hanya dapat digunakan dengan argumen tipe tunggal. Selalu lebih suka gaya .NET, kecuali untuk enam jenis tertentu:

  1. Untuk F# List, gunakan bentuk postfix: int list bukan list<int>.
  2. Untuk Opsi F#, gunakan jenis postfix: int option bukan option<int>.
  3. Untuk Opsi Nilai F#, gunakan bentuk 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>.
  6. Untuk Urutan F#, gunakan bentuk postfix: int seq daripada seq<int>.

Untuk semua jenis lainnya, gunakan bentuk 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, dengan satu tingkat indentasi.

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 gaya Stroustrup:

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

Memformat anotasi tipe pengembalian

Dalam anotasi jenis pengembalian pada fungsi atau anggota, 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 tipe fungsi lengkap dalam tanda tangan, terkadang perlu untuk membagi argumen ke dalam 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 menerima campuran argumen curried dan tuple:

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

Dalam berkas tanda tangan yang sesuai, jenis yang didahului oleh tuple diberi lekukan

// ✔️ 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 hanya parameter/batasan jenis saja yang cocok, letakkan 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 tanpa syarat apa pun.

// ✔️ 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 klausa inherit pada baris baru, diindentasi dengan 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

Ketika sebuah klausa inherit adalah bagian dari catatan, letakkan di baris yang sama apabila pendek. Dan letakkan di baris berikutnya, dengan satu tingkat indentasi, jika panjang atau terdiri dari beberapa baris.

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 }

Pemformatan atribut

Atribut ditempatkan di atas konstruk:

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

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

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

Mereka harus mengikuti segala dokumentasi XML.

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