Indications de mise en forme du code F#
Cet article fournit des instructions pour mettre en forme votre code afin que votre code F# soit :
- Plus lisible
- Conforme aux conventions appliquées par les outils de mise en forme dans Visual Studio Code et d’autres éditeurs
- Similaire à d’autres codes en ligne
Consultez également Conventions de codage de codage et Indication de conception de composants, qui couvrent également les conventions d’affectation de noms.
Mise en forme automatique du code
Le formateur de code Fantomas est l’outil standard de la communauté F# pour la mise en forme automatique du code. Les paramètres par défaut correspondent à ce guide de style.
Nous vous recommandons vivement d’utiliser ce formateur de code. Au sein des équipes F#, les spécifications de mise en forme du code doivent être acceptées et codifiées en termes de fichier de paramètres convenu pour le formateur de code archivé dans le référentiel d’équipe.
Règles générales de mise en forme
Par défaut, F# utilise un espace blanc significatif et respecte cet espace blanc. Les instructions suivantes sont destinées à fournir des conseils sur la façon de négocier certains défis que cela impose.
Utiliser des espaces et non des tabulations
Lorsque la mise en retrait est requise, vous devez utiliser des espaces, et non des tabulations. Le code F# n’utilise pas de tabulations et le compilateur génère une erreur si un caractère de tabulation se trouve en dehors d’un littéral de chaîne ou d’un commentaire.
Utiliser une mise en retrait cohérente
Lors de la mise en retrait, au moins un espace est nécessaire. Votre organisation peut créer des normes de codage pour spécifier le nombre d’espaces à utiliser pour la mise en retrait ; il est courant d’utiliser deux, trois ou quatre espaces de mise en retrait à chaque niveau où la mise en retrait se produit.
Nous vous recommandons quatre espaces par mise en retrait.
Cela dit, la mise en retrait des programmes est une question subjective. Des variantes sont possibles, mais la première règle à suivre est la cohérence de la mise en retrait. Choisissez un style de mise en retrait généralement accepté et utilisez-le systématiquement dans votre codebase.
Éviter la mise en forme sensible à la longueur du nom
Essayez d’éviter la mise en retrait et l’alignement sensibles à l’attribution des noms :
// ✔️ 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 _ -> ()
| ...
Les principales raisons de l’éviter sont les suivantes :
- Le code important est déplacé à droite
- La largeur à gauche est moindre pour le code réel
- Le changement de nom peut interrompre l’alignement
Éviter les espaces blancs superflus
Évitez les espaces blancs superflus dans le code F#, sauf s’ils sont décrits dans ce guide de style.
// ✔️ OK
spam (ham 1)
// ❌ Not OK
spam ( ham 1 )
Mise en forme des commentaires
Préférez plusieurs commentaires à double barre oblique aux commentaires de bloc.
// 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.
*)
Les commentaires doivent mettre en majuscule la première lettre et correspondre à des phrases bien formées.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Pour la mise en forme des commentaires de documentation XML, consultez « Mise en forme des déclarations » ci-dessous.
Mise en forme des expressions
Cette section décrit la mise en forme des expressions de différents types.
Mise en forme des expressions de chaîne
Les littéraux de chaîne et les chaînes interpolées peuvent simplement être laissés sur une seule ligne, quelle que soit la longueur de la ligne.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
Les expressions interpolées à plusieurs lignes sont déconseillées. Liez plutôt le résultat de l’expression à une valeur et utilisez-le dans la chaîne interpolée.
Mise en forme des expressions de tuple
Une instanciation de tuple doit se présenter entre parenthèses, et les virgules de limitation à l’intérieur de celui-ci doivent être suivies d’un espace unique, par exemple : (1, 2)
, (x, y, z)
.
// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]
Il est communément admis d'omettre les parenthèses dans les critères spéciaux des tuples :
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
Il est également communément admis d’omettre les parenthèses si le tuple correspond à la valeur renvoyée d’une fonction :
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
En résumé, préférez les instanciations de tuples entre parenthèses, mais lorsque vous utilisez des tuples pour des critères spéciaux ou une valeur renvoyée, il est communément admis d’éviter les parenthèses.
Mise en forme des expressions d’application
Lors de la mise en forme d’une fonction ou d’une application de méthode, les arguments sont fournis sur la même ligne si la largeur de ligne autorise ce qui suit :
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Omettez les parenthèses, sauf si les arguments les exigent :
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)
// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)
N’omettez pas les espaces lors de l’appel avec plusieurs arguments curryfiés :
// ✔️ 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)
Dans les conventions de mise en forme par défaut, un espace est ajouté lors de l’application de fonctions minuscules à des arguments tuplés ou entre parenthèses (même lorsqu’un seul argument est utilisé) :
// ✔️ 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)
Dans les conventions de mise en forme par défaut, aucun espace n’est ajouté lors de l’application de méthodes majuscules à des arguments tuplés. Cela est dû au fait qu’ils sont souvent utilisés avec la programmation Fluent :
// ✔️ 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)
Vous devrez peut-être passer des arguments à une fonction sur une nouvelle ligne pour plus de lisibilité ou si la liste des arguments ou les noms d’arguments sont trop longs. Dans ce cas, mettez en retrait un niveau :
// ✔️ 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)
Lorsque la fonction utilise un seul argument tuplé à plusieurs lignes, placez chaque argument sur une nouvelle ligne :
// ✔️ 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)
Si les expressions d’argument sont courtes, séparez les arguments avec des espaces et conservez-les sur une seule ligne.
// ✔️ OK
let person = new Person(a1, a2)
// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)
// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)
Si les expressions d’argument sont longues, utilisez de nouvelles lignes et une mise en retrait à un niveau plutôt qu’une mise en retrait vers la parenthèse gauche.
// ✔️ 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)
Ces mêmes règles s’appliquent en présence d’un seul argument multiligne, y compris de chaînes multilignes :
// ✔️ 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" ]
)
Mise en forme des expressions de pipeline
Lors de l’utilisation de plusieurs lignes, les opérateurs de pipeline |>
doivent se trouver sous les expressions sur lesquelles ils opèrent.
// ✔️ 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
Mise en forme d’expressions lambda
Lorsqu’une expression lambda est utilisée en tant qu’argument dans une expression multiligne et suivie d’autres arguments, placez le corps d’une expression lambda sur une nouvelle ligne, avec mise en retrait d’un niveau :
// ✔️ OK
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")
list1
Si l’argument lambda correspond au dernier argument d’une application de fonction, placez tous les arguments jusqu’à la flèche sur la même ligne.
// ✔️ 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}")
Traitez les correspondances lambda de la même manière.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
En présence de nombreux arguments de début ou multilignes avant l’expression lambda, mettez en retrait tous les arguments d’un niveau.
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Si le corps d’une expression lambda compte plusieurs lignes, envisagez de la refactoriser dans une fonction délimitée localement.
Lorsque les pipelines incluent des expressions lambda, chaque expression lambda correspond généralement au dernier argument de chaque étape du pipeline :
// ✔️ OK, with 4 spaces indentation
let printListWithOffsetPiped list1 =
list1
|> List.map (fun elem -> elem + 1)
|> List.iter (fun elem ->
// one indent starting from the pipe
printfn $"A very long line to format the value: %d{elem}")
// ✔️ OK, with 2 spaces indentation
let printListWithOffsetPiped list1 =
list1
|> List.map (fun elem -> elem + 1)
|> List.iter (fun elem ->
// one indent starting from the pipe
printfn $"A very long line to format the value: %d{elem}")
Si les arguments d’une expression lambda ne tiennent pas sur une ligne ou sont eux-mêmes multilignes, placez-les sur la ligne suivante, avec mise en retrait d’un niveau.
// ✔️ 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 ()
Mise en forme des expressions arithmétiques et binaires
Utilisez toujours un espace blanc autour des expressions arithmétiques binaires :
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
Ne pas entourer un opérateur binaire -
lorsqu’il est combiné à certains choix de mise en forme, peut conduire à l’interpréter en tant qu’opérateur unaire -
.
Les opérateurs unaires -
doivent toujours être immédiatement suivis de la valeur qu’ils annulent :
// ✔️ OK
let negate x = -x
// ❌ Not OK
let negateBad x = - x
L’ajout d’un caractère d’espace blanc après l’opérateur -
peut être source de confusion.
Séparez les opérateurs binaires par espaces. Les expressions infixes sont possibles à des fins d’alignement sur une même colonne :
// ✔️ OK
let function1 () =
acc +
(someFunction
x.IngredientName x.Quantity)
// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Cette règle s’applique également aux unités de mesures dans les types et les annotations constantes :
// ✔️ 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)> }
Les opérateurs suivants sont définis dans la bibliothèque standard F# et doivent être utilisés plutôt que de définir des équivalents. L’utilisation de ces opérateurs est recommandée, car elle tend à rendre le code plus lisible et idiomatique. La liste suivante récapitule les opérateurs F# recommandés.
// ✔️ 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
Mise en forme des expressions d’opérateur de plage
Ajoutez uniquement des espaces autour de ..
lorsque toutes les expressions ne sont pas atomiques.
Les entiers et les identificateurs de mot unique sont considérés comme atomiques.
// ✔️ 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 ]
Ces règles s’appliquent également au découpage :
// ✔️ OK
arr[0..10]
list[..^1]
Mise en forme des expressions if
La mise en retrait des conditions dépend de la taille et de la complexité des expressions qui les composent. Écrivez-les sur une ligne si :
cond
,e1
ete2
sont courts.e1
ete2
ne sont pas elles-mêmes des expressionsif/then/else
.
// ✔️ OK
if cond then e1 else e2
Si l’autre expression est absente, il est recommandé de ne jamais écrire l’expression entière sur une seule ligne. Il s’agit de différencier le code impératif de l’élément fonctionnel.
// ✔️ OK
if a then
()
// ❌ Not OK, code formatters will reformat to the above by default
if a then ()
Si l’une des expressions est multiligne, chaque branche conditionnelle doit être multiligne.
// ✔️ OK
if cond then
let e1 = something()
e1
else
e2
// ❌ Not OK
if cond then
let e1 = something()
e1
else e2
Plusieurs conditions avec elif
et else
sont mises en retrait dans la même étendue que if
lorsqu’elles suivent les règles des expressions if/then/else
de ligne.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Si l’une des conditions ou expressions est multiligne, l’expression entière if/then/else
est multiligne :
// ✔️ 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
Si une condition est multiligne ou dépasse la tolérance par défaut de la ligne unique, l’expression de condition doit utiliser une mise en retrait et une nouvelle ligne.
if
et le mot clé then
doivent s’aligner lors de l’encapsulation de l’expression de condition longue.
// ✔️ 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
Toutefois, il est préférable de refactoriser les conditions longues pour une liaison let ou une fonction distincte :
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
if performAction then
e1
else
e2
Mise en forme des expressions de cas d’union
L’application de cas d’union discriminatoire suit les mêmes règles que les applications de fonction et de méthode. Autrement dit, le nom étant mis en majuscules, les formateurs de code suppriment un espace avant un tuple :
// ✔️ OK
let opt = Some("A", 1)
// OK, but code formatters will remove the space
let opt = Some ("A", 1)
Comme les applications de fonction, les constructions qui se répartissent sur plusieurs lignes doivent utiliser la mise en retrait :
// ✔️ OK
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
Mise en forme des expressions de liste et de tableau
Écrivez x :: l
avec des espaces autour de l’opérateur ::
(::
est un opérateur infixe, par conséquent entouré d’espaces).
Les listes et tableaux déclarés sur une seule ligne doivent présenter un espace après le crochet ouvrant et avant le crochet fermant :
// ✔️ OK
let xs = [ 1; 2; 3 ]
// ✔️ OK
let ys = [| 1; 2; 3; |]
Utilisez toujours au moins un espace entre deux opérateurs de type accolades distincts. Par exemple, laissez un espace entre un [
et un {
.
// ✔️ 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 }]
La même directive s’applique aux listes ou tableaux de tuples.
Les listes et tableaux qui se répartissent sur plusieurs lignes suivent une règle similaire à celle des enregistrements :
// ✔️ 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 |] |]
Comme pour les enregistrements, la déclaration des crochets ouvrants et fermants sur leur propre ligne facilite le déplacement du code et son insertion dans les fonctions :
// ✔️ 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 |]
|]
Si une expression de liste ou de tableau correspond au côté droit d’une liaison, vous pouvez préférer l’utilisation du style Stroustrup
:
// ✔️ OK
let pascalsTriangle = [|
[| 1 |]
[| 1; 1 |]
[| 1; 2; 1 |]
[| 1; 3; 3; 1 |]
[| 1; 4; 6; 4; 1 |]
[| 1; 5; 10; 10; 5; 1 |]
[| 1; 6; 15; 20; 15; 6; 1 |]
[| 1; 7; 21; 35; 35; 21; 7; 1 |]
[| 1; 8; 28; 56; 70; 56; 28; 8; 1 |]
|]
Toutefois, lorsqu’une expression de liste ou de tableau ne correspond pas au côté droit d’une liaison, par exemple lorsqu’elle se trouve à l’intérieur d’une liste ou d’un tableau autre, si cette expression interne doit s’étendre sur plusieurs lignes, les crochets doivent se trouver sur leurs propres lignes :
// ✔️ 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
] ]
Cette même règle s’applique aux types d’enregistrements à l’intérieur des tableaux/listes :
// ✔️ 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
} ]
Lors de la génération de tableaux et de listes par programmation, préférez ->
plutôt que do ... yield
lorsqu’une valeur est toujours générée :
// ✔️ 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 ]
Les versions antérieures de F# nécessitaient la spécification de yield
dans les situations où les données pouvaient être générées conditionnellement ou en présence d’expressions consécutives à évaluer. Il est préférable d’omettre ces mots clés yield
, sauf si vous devez compiler avec une version antérieure du langage F# :
// ✔️ 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"
]
Dans certains cas, do...yield
peut faciliter la lisibilité. Ces cas, bien que subjectifs, doivent être pris en considération.
Mise en forme des expressions d’enregistrement
Les enregistrements courts peuvent être écrits sur une seule ligne :
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
Les enregistrements plus longs doivent utiliser de nouvelles lignes pour les étiquettes :
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Styles de mise en forme entre crochets multilignes
Pour les enregistrements qui s’étendent sur plusieurs lignes, il existe trois styles de mise en forme couramment utilisés : Cramped
, Aligned
et Stroustrup
. Le style Cramped
est le style par défaut pour le code F#, car il tend à privilégier les styles qui permettent au compilateur d’analyser facilement le code. Les styles Aligned
et Stroustrup
permettent de réorganiser plus facilement les membres, ce qui peut être plus facile à refactoriser, mais certaines situations peuvent nécessiter un code légèrement plus détaillé.
Cramped
: format d’enregistrement F# par défaut et standard historique. Les crochets ouvrants sont sur la même ligne que le premier membre et le crochet fermant sur la même ligne que le dernier membre.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Aligned
: les crochets ont chacun leur propre ligne, alignés sur la même colonne.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }
Stroustrup
:le crochet ouvrant se trouve sur la même ligne que la liaison et le crochet fermant a sa propre ligne.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Les mêmes règles de style de mise en forme s’appliquent aux éléments de liste et de tableau.
Mise en forme des expressions d’enregistrement de copie et de mise à jour
Une expression d’enregistrement de copie et de mise à jour est toujours un enregistrement et dès lors, des instructions similaires s’appliquent.
Les expressions courtes peuvent tenir sur une seule ligne :
// ✔️ OK
let point2 = { point with X = 1; Y = 2 }
Les expressions plus longues doivent utiliser de nouvelles lignes et être mises en forme en fonction de l’une des conventions nommées ci-dessus :
// ✔️ 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 = ""
}
}
Remarque : si vous utilisez le style Stroustrup
pour les expressions de copie et de mise à jour, vous devez mettre en retrait les membres au-delà nom de l’enregistrement copié :
// ✔️ 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"
}
Mise en forme des critères spéciaux
Utilisez un |
pour chaque clause d’une correspondance sans retrait. En présence d’une expression courte, vous pouvez envisager d’utiliser une seule ligne si chaque sous-expression est également simple.
// ✔️ 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"
Si l’expression à droite de la flèche de critères spéciaux est trop longue, déplacez-la vers la ligne suivante, avec mise en retrait d’une étape à partir de match
/|
.
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Comme pour les conditions if importantes, si une expression de correspondance est multiligne ou dépasse la tolérance par défaut de ligne unique, l’expression de correspondance doit utiliser une mise en retrait et une nouvelle ligne.
match
et le mot clé with
doivent s’aligner lors de l’encapsulation de l’expression de correspondance longue.
// ✔️ OK, but better to refactor, see below
match
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
with
| X y -> y
| _ -> 0
Toutefois, il est préférable de refactoriser les expressions de correspondance longues pour une liaison let ou une fonction distincte :
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
match performAction with
| X y -> y
| _ -> 0
L’alignement des flèches de critères spéciaux doit être évité.
// ✔️ 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
Les critères spéciaux introduits à l’aide du mot clé function
doivent présenter une mise en retrait d’un niveau à partir du début de la ligne précédente :
// ✔️ OK
lambdaList
|> List.map (function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
L’utilisation de function
dans les fonctions définies par let
ou let rec
doit généralement être évitée pour privilégier match
. Si elles sont utilisées, les règles de modèle doivent s’aligner sur le mot clé 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
Mise en forme des expressions try/with
Les critères spéciaux du type d’exception doivent être mis en retrait au même niveau que 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"
Ajoutez un |
pour chaque clause, sauf s’il n’existe qu’une seule clause :
// ✔️ 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
Mise en forme des arguments nommés
Les arguments nommés doivent avoir des espaces entourant =
:
// ✔️ 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)
Lors de l’utilisation de critères spéciaux à l’aide d’unions discriminatoires, les modèles nommés sont mis en forme de la même façon, par exemple.
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
Mise en forme des expressions de mutation
Généralement, les expressions de mutation location <- expr
sont mises en forme sur une ligne.
Si une mise en forme multiligne est requise, placez l’expression de droite sur une nouvelle ligne.
// ✔️ 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
Mise en forme des expressions d’objet
Les membres des expressions d’objet doivent être alignés sur member
avec mise en retrait d’un niveau.
// ✔️ 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) }
Vous pouvez également préférer utiliser le style Stroustrup
:
let comparer = {
new IComparer<string> with
member x.Compare(s1, s2) =
let rev (s: String) = new String(Array.rev (s.ToCharArray()))
let reversed = rev s1
reversed.CompareTo(rev s2)
}
Les définitions de type vides peuvent être mises en forme sur une ligne :
type AnEmptyType = class end
Quelle que soit la largeur de page choisie, = class end
doit toujours se trouver sur la même ligne.
Mise en forme des expressions d’index/de section
Les expressions d’index ne doivent contenir aucun espace autour des crochets ouvrants et fermants.
// ✔️ OK
let v = expr[idx]
let y = myList[0..1]
// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]
Cela s’applique également à l’ancienne syntaxe expr.[idx]
.
// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]
// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]
Mise en forme des expressions entre guillemets
Les symboles délimiteurs (<@
, , @>
, @@>
<@@
) doivent être placés sur des lignes distinctes si l’expression entre guillemets est une expression à plusieurs lignes.
// ✔️ OK
<@
let f x = x + 10
f 20
@>
// ❌ Not OK
<@ let f x = x + 10
f 20
@>
Dans les expressions à une seule ligne, les symboles délimiteurs doivent être placés sur la même ligne que l’expression elle-même.
// ✔️ OK
<@ 1 + 1 @>
// ❌ Not OK
<@
1 + 1
@>
Mise en forme des expressions chaînées
Lorsque les expressions chaînées (applications de fonction entrelacées avec .
) sont longues, placez chaque appel d’application sur la ligne suivante.
Mettez en retrait les liaisons suivantes de la chaîne d’un niveau après la liaison de début.
// ✔️ OK
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
// ✔️ OK
Cli
.Wrap("git")
.WithArguments(arguments)
.WithWorkingDirectory(__SOURCE_DIRECTORY__)
.ExecuteBufferedAsync()
.Task
La liaison de début peut être composée de plusieurs liaisons s’il s’agit d’identificateurs simples. Par exemple, l’ajout d’un espace de noms complet.
// ✔️ OK
Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
Les liaisons suivantes doivent également contenir des identificateurs simples.
// ✔️ 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))
Si les arguments d’une application de fonction ne tiennent pas sur le reste de la ligne, placez chaque argument sur la ligne suivante.
// ✔️ 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
Les arguments lambda d’une application de fonction doivent commencer sur la même ligne que le (
ouvrant.
// ✔️ OK
builder
.WithEnvironment()
.WithLogger(fun loggerConfiguration ->
// ...
())
// ❌ Not OK, formatting tools will reformat to the above
builder
.WithEnvironment()
.WithLogger(
fun loggerConfiguration ->
// ...
())
Mise en forme des déclarations
Cette section décrit la mise en forme des déclarations de différents types.
Ajouter des lignes vides entre les déclarations
Séparez les définitions de fonction et de classe de niveau supérieur avec une seule ligne vide. Par exemple :
// ✔️ 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
Si une construction comporte des commentaires de document XML, ajoutez une ligne vide avant le commentaire.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Mise en forme des déclarations let et membres
Lors de la mise en forme de déclarations let
et member
, le côté droit d’une liaison passe généralement sur une ligne, ou (s’il est trop long) sur une nouvelle ligne mise en retrait d’un niveau.
Les exemples suivants sont conformes :
// ✔️ 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
Ceux-ci ne sont pas conformes :
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
let d = while f do
printfn "%A" x
Les instanciations de type d’enregistrement peuvent également placer les crochets sur leurs propres lignes :
// ✔️ OK
let bilbo =
{
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Vous pouvez également préférer utiliser le style Stroustrup
, avec le {
ouvrant sur la même ligne que le nom de liaison :
// ✔️ OK
let bilbo = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Séparez les membres avec une seule ligne vide et un document et ajoutez un commentaire de documentation :
// ✔️ OK
/// This is a thing
type ThisThing(value: int) =
/// Gets the value
member _.Value = value
/// Returns twice the value
member _.TwiceValue() = value*2
Des lignes vides supplémentaires peuvent être utilisées (avec parcimonie) pour séparer les groupes de fonctions associées. Les lignes vides peuvent être omises entre plusieurs unilignes associées (par exemple, un ensemble d’implémentations factices). Utilisez des lignes vides dans les fonctions, avec parcimonie, pour indiquer des sections logiques.
Mise en forme des arguments de fonction et de membre
Lors de la définition d’une fonction, utilisez un espace blanc autour de chaque argument.
// ✔️ OK
let myFun (a: decimal) (b: int) c = a + b + c
// ❌ Not OK, code formatters will reformat to the above by default
let myFunBad (a:decimal)(b:int)c = a + b + c
En présence d’une définition de fonction longue, placez les paramètres sur de nouvelles lignes et mettez-les en retrait pour qu’ils correspondent au niveau de retrait du paramètre suivant.
// ✔️ 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
Cela s’applique également aux membres, constructeurs et paramètres utilisant des tuples :
// ✔️ 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
Si les paramètres sont curryfiés, placez le caractère =
avec n’importe quel type de retour sur une nouvelle ligne :
// ✔️ 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
Cela permet d’éviter les lignes trop longues (dans le cas où le type de retour peut avoir un nom long) et de limiter les dommages de ligne lors de l’ajout de paramètres.
Mise en forme des déclarations d’opérateur
Vous pouvez utiliser un espace blanc pour entourer une définition d’opérateur :
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
Pour tout opérateur personnalisé commençant par *
et doté de plusieurs caractères, vous devez ajouter un espace blanc au début de la définition afin d’éviter une ambiguité du compilateur. Dès lors, nous vous recommandons simplement d’entourer les définitions de tous les opérateurs avec un seul caractère d’espace blanc.
Mise en forme des déclarations d’enregistrement
Pour les déclarations d’enregistrement, par défaut, vous devez mettre en retrait le {
dans la définition de type par quatre espaces, démarrer la liste des étiquettes sur la même ligne et aligner les membres, le cas échéant, avec le jeton {
:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
Il est également courant de placer des crochets sur leur propre ligne, avec des étiquettes mises en retrait par quatre espaces supplémentaires :
// ✔️ OK
type PostalAddress =
{
Address: string
City: string
Zip: string
}
Vous pouvez également placer le {
à la fin de la première ligne de définition de type (style Stroustrup
) :
// ✔️ OK
type PostalAddress = {
Address: string
City: string
Zip: string
}
Si des membres supplémentaires sont nécessaires, évitez with
/end
chaque fois que possible :
// ✔️ 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
Il existe une exception à cette règle de style si vous mettez en forme des enregistrements en fonction du style Stroustrup
. Dans ce cas, en raison de règles de compilateur, le mot clé with
est requis si vous souhaitez implémenter une interface ou ajouter des membres supplémentaires :
// ✔️ 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}"
Lorsqu’une documentation XML est ajoutée pour les champs d’enregistrement, le style Aligned
ou Stroustrup
est privilégié et un espace blanc supplémentaire doit être ajouté entre les membres :
// ❌ 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}"
Il est préférable de placer le jeton d’ouverture et le jeton de fermeture sur une nouvelle ligne si vous déclarez des implémentations d’interface ou des membres sur l’enregistrement :
// ✔️ 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
Ces mêmes règles s’appliquent aux alias de type d’enregistrement anonyme.
Mise en forme des déclarations d’union discriminée
Pour les déclarations d’union discriminées, mettez en retrait |
dans la définition de type par quatre espaces :
// ✔️ 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
En présence d’une seule union courte, vous pouvez omettre le premier |
.
// ✔️ OK
type Address = Address of string
Pour une union plus longue ou multiligne, conservez le |
et placez chaque champ d’union sur une nouvelle ligne, avec le *
de séparation à la fin de chaque ligne.
// ✔️ 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
Lorsque des commentaires de documentation sont ajoutés, utilisez une ligne vide avant chaque commentaire ///
.
// ✔️ 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
Mise en forme des déclarations de littéraux
Les littéraux F# utilisant l’attribut Literal
doivent placer l’attribut sur sa propre ligne et utiliser la casse Pascal :
// ✔️ OK
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Évitez de placer l’attribut sur la même ligne que la valeur.
Mise en forme des déclarations de module
Le code d’un module local doit être mis en retrait par rapport au module, mais le code d’un module de niveau supérieur ne doit pas l’être. Il n’est pas nécessaire de mettre en retrait les éléments d’espace de noms.
// ✔️ 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
Mise en forme des déclarations do
Dans les déclarations de type, les déclarations de module et les expressions de calcul, l’utilisation de do
ou do!
est parfois nécessaire pour les opérations à effet secondaire.
Lorsqu’elles s’étendent sur plusieurs lignes, utilisez la mise en retrait et une nouvelle ligne pour une mise en retrait cohérente avec let
/let!
. Voici un exemple d’utilisation de do
dans une classe :
// ✔️ 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
Voici un exemple avec do!
utilisant deux espaces de mise en retrait (car avec do!
il n’existe aucune différence entre les approches lors de l’utilisation de quatre espaces de mise en retrait) :
// ✔️ 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
}
Mise en forme des opérations d’expression de calcul
Lors de la création d’opérations personnalisées pour les expressions de calcul, il est recommandé d’utiliser la casse mixte :
// ✔️ 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
}
Le domaine qui est modélisé doit déterminer la convention d’affectation de noms. S’il est idiomatique d’utiliser une autre convention, cette convention doit être utilisée.
Si la valeur renvoyée d’une expression est une expression de calcul, il est préférable de placer le nom du mot clé d’expression de calcul sur sa propre ligne :
// ✔️ OK
let foo () =
async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Vous pouvez également préférer placer l’expression de calcul sur la même ligne que le nom de liaison :
// ✔️ OK
let foo () = async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Quelle que soit votre préférence, vous devez vous efforcer de rester cohérent dans votre codebase. Les formateurs peuvent vous permettre de spécifier cette préférence à des fins de cohérence.
Mise en forme des types et annotations de type
Cette section décrit la mise en forme des types et annotations de type. Cela inclut la mise en forme des fichiers de signature avec l’extension .fsi
.
Pour les types, préférez la syntaxe de préfixe pour les génériques (Foo<T>
), avec certaines exceptions spécifiques
F# permet le style suffixé d’écriture de types génériques (par exemple, int list
) et le style préfixé (par exemple, list<int>
).
Le style suffixé ne peut être utilisé qu’avec un seul argument de type.
Privilégiez toujours le style .NET, sauf pour cinq types spécifiques :
- Pour les listes F#, utilisez le formulaire suffixé :
int list
plutôt quelist<int>
. - Pour les options F#, utilisez le formulaire suffixé :
int option
plutôt queoption<int>
. - Pour les options de valeur F#, utilisez le formulaire suffixé :
int voption
plutôt quevoption<int>
. - Pour les tableaux F#, utilisez le formulaire suffixé :
int array
plutôt quearray<int>
ouint[]
. - Pour les cellules de référence, utilisez
int ref
plutôt queref<int>
ouRef<int>
.
Pour tous les autres types, utilisez le formulaire préfixé.
Mise en forme des types de fonctions
Lorsque vous définissez la signature d’une fonction, utilisez un espace blanc autour du symbole ->
:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Mise en forme des annotations de type valeur et argument
Lorsque vous définissez des valeurs ou des arguments avec des annotations de type, utilisez un espace blanc après le symbole :
, mais pas avant :
// ✔️ 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
Mise en forme des annotations de type multiligne
Lorsqu’une annotation de type est longue ou multiligne, placez-la sur la ligne suivante, mise en retrait d’un niveau.
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
Pour les types d’enregistrements anonymes inlined, vous pouvez également utiliser le style Stroustrup
:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Mise en forme des annotations de type de retour
Dans les annotations de type de retour de fonction ou de membre, utilisez un espace blanc avant et après le symbole :
:
// ✔️ 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
Types de mise en forme dans les signatures
Lorsque vous écrivez des types de fonctions complets dans des signatures, il est parfois nécessaire de fractionner les arguments sur plusieurs lignes. Le type de retour est toujours mis en retrait.
Pour une fonction tuplée, les arguments sont séparés par *
, placés à la fin de chaque ligne.
Par exemple, considérez une fonction avec l’implémentation suivante :
let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
Dans le fichier de signature (extension .fsi
) correspondant, la fonction peut être mise en forme comme suit lorsqu’une mise en forme multiligne est requise :
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list
De même, envisagez une fonction curryfiée :
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
Dans le fichier de signature correspondant, les éléments ->
sont placés à la fin de chaque ligne :
// ✔️ OK
val SampleCurriedFunction:
arg1: string ->
arg2: string ->
arg3: int ->
arg4: int ->
int list
De même, envisagez une fonction combinant des arguments curryfiés et tuplés :
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
Dans le fichier de signature correspondant, les types précédés d’un tuple sont mis en retrait.
// ✔️ 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
Les mêmes règles s’appliquent aux membres des signatures de type :
type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string
Mise en forme d’arguments et de contraintes de type générique explicites
Les instructions ci-dessous s’appliquent aux définitions de fonction, de membre et de type, ainsi qu’aux applications de fonction.
Conservez les arguments et contraintes de type générique sur une seule ligne s’ils ne sont pas trop longs :
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Si les arguments/contraintes de type générique et les paramètres de fonction ne conviennent pas, mais que les paramètres/contraintes de type seuls conviennent, placez les paramètres sur de nouvelles lignes :
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
param
=
// function body
Si les paramètres ou les contraintes de type sont trop longs, scindez-les et alignez-les comme indiqué ci-dessous. Conservez la liste des paramètres de type sur la même ligne que la fonction, quelle que soit sa longueur. Pour les contraintes, placez when
sur la première ligne et conservez chaque contrainte sur une seule ligne, quelle que soit sa longueur. Placez >
à la fin de la dernière ligne. Mettre en retrait les contraintes d’un niveau.
// ✔️ 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
Si les paramètres/contraintes de type sont scindés, mais qu’il n’existe pas de paramètres de fonction normaux, placez =
sur une nouvelle ligne :
// ✔️ 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
Les mêmes règles s’appliquent aux applications de fonction :
// ✔️ 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
Mise en forme de l’héritage
Les arguments du constructeur de classe de base apparaissent dans la liste d’arguments de la clause inherit
.
Placez la clause inherit
sur une nouvelle ligne, mise en retrait d’un niveau.
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)
En présence d’un constructeur long ou multiligne, placez-le sur la ligne suivante, avec mise en retrait d’un niveau.
Mettez en forme ce constructeur multiligne en fonction des règles des applications de fonction multiligne.
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
""")
Mise en forme du constructeur principal
Dans les conventions de mise en forme par défaut, aucun espace n’est ajouté entre le nom de type et les parenthèses du constructeur principal.
// ✔️ 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
Plusieurs constructeurs
Lorsque la clause inherit
fait partie d’un enregistrement, placez-la sur la même ligne si elle est courte.
Placez-la sur la ligne suivante, avec mise en retrait d’un niveau, si elle est longue ou multiligne.
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 }
Mise en forme des attributs
Les attributs sont placés au-dessus d’une construction :
// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...
// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x
// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
Ils doivent suivre n’importe quelle documentation XML :
// ✔️ OK
/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x
Mise en forme des attributs sur des paramètres
Les attributs peuvent également être placés sur des paramètres. Dans ce cas, placez-les sur la même ligne que le paramètre et avant le nom :
// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)
Mise en forme de plusieurs attributs
Lorsque plusieurs attributs sont appliqués à une construction autre qu’un paramètre, placez chaque attribut sur une ligne distincte :
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
Lorsqu’ils sont appliqués à un paramètre, placez les attributs sur la même ligne et séparez-les par un séparateur ;
.
Remerciements
Ces instructions sont basées sur le Guide complet des conventions de mise en forme F# par Anh-Dung Phan.