Instrucciones de formato de código de F#
En este artículo se ofrecen instrucciones sobre cómo dar formato al código para que el código de F# sea:
- Más legible
- De acuerdo con las convenciones aplicadas por las herramientas de formato de Visual Studio Code y otros editores
- Similar a otro código en línea
Estas directrices se basan en una guía completa sobre las convenciones de formato de F#, de Anh-Dung Phan.
Consulte también Convenciones de codificación y Directrices de diseño de componentes, que también trata las convenciones de nomenclatura.
Reglas generales para el formato
F# usa un espacio en blanco significativo de forma predeterminada y es sensible al espacio en blanco. Las siguientes directrices están diseñadas para proporcionar instrucciones sobre cómo hacer malabares con algunos desafíos que esto puede imponer.
Usar espacios no pestañas
Cuando se requiere sangría, debe usar espacios, no tabulaciones. El código de F# no usa tabulaciones y el compilador producirá un error si se encuentra un carácter de tabulación fuera de un literal de cadena o comentario.
Uso de sangría coherente
Al aplicar sangría, se requiere al menos un espacio. Su organización puede crear estándares de codificación para especificar el número de espacios que se usarán para la sangría. dos, tres o cuatro espacios de sangría en cada nivel donde se produce la sangría es habitual.
Se recomiendan cuatro espacios por sangría.
Dicho esto, la sangría de los programas es una cuestión subjetiva. Las variaciones son correctas, pero la primera regla que debe seguir es la coherencia de la sangría. Elija un estilo de sangría aceptado con carácter general y úselo sistemáticamente en todo el código base.
Uso de un formateador de código automático
El formateador de código Fanbrus es la herramienta estándar de la comunidad de F# para el formato automático de código. La configuración predeterminada corresponde a esta guía de estilo.
Se recomienda encarecidamente el uso de este formateador de código. En los equipos de F#, las especificaciones de formato de código deben acordarse y codificarse en términos de un archivo de configuración acordado para el formateador de código que se ha registrado en el repositorio del equipo.
Evitar el formato que sea sensible a la longitud del nombre
Busque evitar la sangría y la alineación que sea sensible a la nomenclatura:
// ✔️ 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 _ -> ()
| ...
Las principales razones para evitar esto son:
- El código importante se mueve lejos a la derecha
- Queda menos ancho para el código real.
- Cambiar el nombre puede interrumpir la alineación
Evitar espacios en blanco extraneosos
Evite espacios en blanco extraneosos en el código de F#, excepto donde se describe en esta guía de estilo.
// ✔️ OK
spam (ham 1)
// ❌ Not OK
spam ( ham 1 )
Aplicar formato a los comentarios
Prefiere varios comentarios de doble barra diagonal sobre los comentarios de bloque.
// 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.
*)
Los comentarios deben incluir en mayúscula la primera letra y ser frases o frases bien formadas.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Para dar formato a los comentarios del documento XML, vea "Formatting declarations" (Formato de declaraciones) a continuación.
Formato de expresiones
En esta sección se describe el formato de expresiones de diferentes tipos.
Formato de expresiones de cadena
Los literales de cadena y las cadenas interpoladas solo se pueden dejar en una sola línea, independientemente de cuánto tiempo sea la línea.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
No se recomiendan las expresiones interpoladas de varias líneas. En su lugar, enlace el resultado de la expresión a un valor y úsalo en la cadena interpolada.
Formato de expresiones de tupla
Una instancia de tupla debe estar entre paréntesis y las comas delimitadoras dentro de ella deben ir seguidas de un solo espacio, por ejemplo: (1, 2) , (x, y, z) .
// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]
Normalmente se acepta omitir paréntesis en la coincidencia de patrones de tuplas:
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
También se acepta normalmente omitir paréntesis si la tupla es el valor devuelto de una función:
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
En resumen, prefiere las instancias de tupla entre paréntesis, pero cuando se usan tuplas para la coincidencia de patrones o un valor devuelto, se considera bien para evitar paréntesis.
Aplicar formato a expresiones de aplicación
Al dar formato a una aplicación de función o método, los argumentos se proporcionan en la misma línea cuando el ancho de línea permite:
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Los paréntesis deben omitirse a menos que los argumentos los requieran:
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1(x.IngredientName)
En las convenciones de formato predeterminadas, se agrega un espacio al aplicar funciones en minúsculas a argumentos tupled o entre paréntesis:
// ✔️ 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)
En las convenciones de formato predeterminadas, no se agrega espacio al aplicar métodos con mayúsculas a argumentos de tupled. Esto se debe a que a menudo se usan con una programación fluida:
// ✔️ 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)
Es posible que tenga que pasar argumentos a una función en una nueva línea, como cuestión de legibilidad o porque la lista de argumentos o los nombres de argumento son demasiado largos. En ese caso, aplique sangría a un nivel:
// ✔️ 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)
Cuando la función toma un único argumento de tuplido de varias líneas, coloque cada argumento en una nueva línea:
// ✔️ 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 las expresiones de argumento son cortas, separe los argumentos con espacios y mantén los argumentos en una línea.
// ✔️ OK
let person = new Person(a1, a2)
// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)
// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)
Si las expresiones de argumento son largas, use nuevas líneas y aplique sangría a un nivel, en lugar de aplicar sangría al paréntesis de la izquierda.
// ✔️ 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)
Las mismas reglas se aplican incluso si solo hay un único argumento de varias líneas, incluidas las cadenas de varias líneas:
// ✔️ 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" ]
)
Formato de expresiones de canalización
Cuando se usan varias líneas, los |> operadores de canalización deben ir debajo de las expresiones en las que operan.
// ✔️ 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
Formato de expresiones lambda
Cuando una expresión lambda se usa como argumento en una expresión de varias líneas y va seguida de otros argumentos, coloque el cuerpo de una expresión lambda en una nueva línea, con sangría por un nivel:
// ✔️ OK
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")
list1
Si el argumento lambda es el último argumento de una aplicación de función, coloque todos los argumentos hasta la flecha en la misma línea.
// ✔️ 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}")
Trate las expresiones lambda de coincidencia de forma similar.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Cuando hay muchos argumentos iniciales o de varias líneas antes de que la expresión lambda aplique sangría a todos los argumentos con un nivel.
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Si el cuerpo de una expresión lambda tiene varias líneas de longitud, debe considerar la posibilidad de refactorizarla en una función con ámbito local.
Cuando las canalizaciones incluyen expresiones lambda, cada expresión lambda suele ser el último argumento en cada fase de la canalización:
// ✔️ 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}")
Aplicar formato a expresiones aritméticas y binarias
Use siempre espacios en blanco alrededor de expresiones aritméticas binarias:
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
Si no se puede rodear un operador binario, cuando se combina con determinadas opciones de formato, podría provocar su interpretación - como - unario.
Los operadores - unarios siempre deben ir seguidos inmediatamente del valor que niegan:
// ✔️ OK
let negate x = -x
// ❌ Not OK
let negateBad x = - x
Agregar un carácter de espacio en blanco después del - operador puede provocar confusión para otros usuarios.
Separe los operadores binarios por espacios. Las expresiones de infijo son correctas para la alineación en la misma columna:
// ✔️ OK
let function1 () =
acc +
(someFunction
x.IngredientName x.Quantity)
// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Los operadores siguientes se definen en la biblioteca estándar de F# y se deben usar en lugar de definir equivalentes. Se recomienda usar estos operadores, ya que tiende a hacer que el código sea más legible e idiomático. En la lista siguiente se resumen los operadores de F# recomendados.
// ✔️ 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
Formato de expresiones de operador de intervalo
Agregue solo espacios alrededor de .. cuando todas las expresiones no sean atómicas.
Los enteros y los identificadores de palabras únicas se consideran atómicos.
// ✔️ 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 ]
Estas reglas también se aplican a lalicación:
// ✔️ OK
arr[0..10]
list[..^1]
Aplicar formato a expresiones if
La sangría de los condicionales depende del tamaño y la complejidad de las expresiones que las conste. Escríbalos en una línea cuando:
cond,e1y sone2cortose1ye2no sonif/then/elseexpresiones en sí.
// ✔️ OK
if cond then e1 else e2
Si alguna de las expresiones es de varias líneas if/then/else o expresiones.
// ✔️ OK
if cond then
e1
else
e2
Se aplica sangría a varios condicionales con y en el mismo ámbito que cuando siguen las reglas elif else de las if expresiones de una if/then/else línea.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Si cualquiera de las condiciones o expresiones es de varias líneas, toda la if/then/else expresión es de varias líneas:
// ✔️ OK
if cond1 then
e1
elif cond2 then
e2
elif cond3 then
e3
else
e4
Si una condición es larga, la then palabra clave todavía se coloca al final de la expresión.
// ✔️ OK, but better to refactor, see below
if
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree then
e1
else
e2
Sin embargo, es mejor estilo refactorizar las condiciones largas en un enlace let o una función independiente:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
if performAction then
e1
else
e2
Formato de expresiones de mayúsculas y minúsculas de unión
La aplicación de casos de unión discriminada sigue las mismas reglas que las aplicaciones de función y método. Es decir, dado que el nombre está en mayúsculas, los formateadores de código quitarán un espacio antes de una tupla:
// ✔️ OK
let opt = Some("A", 1)
// OK, but code formatters will remove the space
let opt = Some ("A", 1)
Al igual que las aplicaciones de función, las construcciones que se dividen entre varias líneas deben usar sangría:
// ✔️ OK
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
Lista de formato y expresiones de matriz
Escriba x :: l con espacios alrededor del operador ( es un operador :: :: infix, por lo que está rodeado de espacios).
La lista y las matrices declaradas en una sola línea deben tener un espacio después del corchete de apertura y antes del corchete de cierre:
// ✔️ OK
let xs = [ 1; 2; 3 ]
// ✔️ OK
let ys = [| 1; 2; 3; |]
Use siempre al menos un espacio entre dos operadores distintos de tipo llave. Por ejemplo, deje un espacio entre y [ { .
// ✔️ 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 misma directriz se aplica a listas o matrices de tuplas.
Las listas y matrices que se dividen entre varias líneas siguen una regla similar a la de los registros:
// ✔️ 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 |] |]
Al igual que con los registros, declarar los corchetes de apertura y cierre en su propia línea facilitará el movimiento de código y la canalización en funciones.
Al generar matrices y listas mediante programación, se prefiere cuando -> siempre se genera un do ... yield valor:
// ✔️ 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 ]
Las versiones anteriores de F# requerían especificar en situaciones en las que los datos se pueden generar condicionalmente o puede haber yield expresiones consecutivas que evaluar. Prefiere omitir estas palabras yield clave a menos que deba compilar con una versión anterior del lenguaje 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"
]
En algunos casos, do...yield puede ayudar a la legibilidad. Estos casos, aunque subjetivos, deben tenerse en cuenta.
Formato de expresiones de registro
Los registros cortos se pueden escribir en una línea:
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
Los registros que sean más largos deben usar nuevas líneas para las etiquetas:
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Las expresiones de campo de registro largo deben usar una nueva línea y tener una sangría desde la { apertura:
{ A = a
B =
someFunctionCall
arg1
arg2
// ...
argX
C = c }
Es posible colocar y en nuevas líneas con contenido con sangría, pero los formateadores de código pueden volver a { } formatear esto de forma predeterminada:
// ✔️ OK
let rainbow =
{ Boss1 = "Jeffrey"
Boss2 = "Jeffrey"
Boss3 = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
// ❌ Not preferred, code formatters will reformat to the above by default
let rainbow =
{
Boss1 = "Jeffrey"
Boss2 = "Jeffrey"
Boss3 = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"]
}
Las mismas reglas se aplican a los elementos de lista y matriz.
Formato de expresiones de registro de copia y actualización
Una expresión de registro de copia y actualización sigue siendo un registro, por lo que se aplican directrices similares.
Las expresiones cortas pueden caber en una línea:
// ✔️ OK
let point2 = { point with X = 1; Y = 2 }
Las expresiones más largas deben usar nuevas líneas:
// ✔️ OK
let rainbow2 =
{ rainbow with
Boss = "Jeffrey"
Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Es posible que quiera dedicar líneas independientes para las llaves y aplicar sangría a un ámbito a la derecha con la expresión ; sin embargo, los formateadores de código pueden . En algunos casos especiales, como ajustar un valor con un opcional sin paréntesis, es posible que tenga que mantener una llave en una línea:
// ✔️ OK
let newState =
{ state with
Foo = Some { F1 = 0; F2 = "" } }
// ❌ Not OK, code formatters will reformat to the above by default
let newState =
{
state with
Foo =
Some {
F1 = 0
F2 = ""
}
}
Coincidencia de patrones de formato
Use para | cada cláusula de una coincidencia sin sangría. Si la expresión es corta, puede considerar el uso de una sola línea si cada subexpresión también es sencilla.
// ✔️ 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 la expresión situada a la derecha de la flecha de coincidencia de patrones es demasiado grande, muévela a la línea siguiente y aplique sangría a un paso de match / | .
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Se debe evitar alinear las flechas de una coincidencia de patrón.
// ✔️ 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
La coincidencia de patrones introducida mediante la palabra clave debe aplicar function sangría a un nivel desde el principio de la línea anterior:
// ✔️ OK
lambdaList
|> List.map (function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
En general, se debe evitar el uso de en funciones definidas por function o en favor de let let rec match . Si se usa, las reglas de patrón deben alinearse con la palabra clave 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
Aplicar formato a expresiones try/with
La coincidencia de patrones en el tipo de excepción debe aplicar sangría al mismo nivel 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"
Agregue un | para cada cláusula, excepto cuando solo hay una cláusula única:
// ✔️ 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
Aplicar formato a argumentos con nombre
Los argumentos con nombre no deben tener espacio alrededor de = :
// ✔️ OK
let makeStreamReader x = new System.IO.StreamReader(path=x)
// ❌ Not OK, no spaces necessary around '=' for named arguments
let makeStreamReader x = new System.IO.StreamReader(path = x)
Cuando la coincidencia de patrones usa uniones discriminadas, los patrones con nombre tienen un formato similar, por ejemplo,
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, no spaces necessary around '=' for named pattern access
let examineData x =
match data with
| OnePartData(part1 = p1) -> p1
| TwoPartData(part1 = p1; part2 = p2) -> p1 + p2
Formato de expresiones de mutación
Normalmente, las location <- expr expresiones de mutación tienen formato en una línea.
Si se requiere formato de varias líneas, coloque la expresión del lado derecho en una nueva línea.
// ✔️ 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
Aplicar formato a expresiones de objeto
Los miembros de expresión de objeto deben alinearse con member la sangría de un nivel.
// ✔️ 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) }
Aplicar formato a expresiones de índice o segmento
Las expresiones de índice no deben contener espacios alrededor de los corchetes de apertura y cierre.
// ✔️ OK
let v = expr[idx]
let y = myList[0..1]
// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]
Esto también se aplica a la sintaxis expr.[idx] anterior.
// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]
// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]
Declaraciones de formato
En esta sección se describe el formato de declaraciones de diferentes tipos.
Agregar líneas en blanco entre declaraciones
Separe las definiciones de función y clase de nivel superior con una sola línea en blanco. Por ejemplo:
// ✔️ 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 una construcción tiene comentarios de documento XML, agregue una línea en blanco antes del comentario.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Dar formato a las declaraciones let y member
Al aplicar formato y declaraciones, el lado derecho de un enlace va en una línea o (si es demasiado largo) va en una nueva línea con sangría de un let member nivel.
Por ejemplo, lo siguiente es compatible:
// ✔️ 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
Los siguientes elementos no son compatibles:
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
type File =
member this.SaveAsync(path: string) : Async<unit> = async {
// IO operation
return ()
}
let c = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
let d = while f do
printfn "%A" x
Separe los miembros con una sola línea en blanco y un documento y agregue un comentario de documentación:
// ✔️ OK
/// This is a thing
type ThisThing(value: int) =
/// Gets the value
member _.Value = value
/// Returns twice the value
member _.TwiceValue() = value*2
Se pueden usar líneas en blanco adicionales (con moderación) para separar grupos de funciones relacionadas. Las líneas en blanco se pueden omitir entre un grupo de one-liners relacionados (por ejemplo, un conjunto de implementaciones ficticias). Use líneas en blanco en funciones, con moderación, para indicar secciones lógicas.
Función de formato y argumentos de miembro
Al definir una función, use espacios en blanco alrededor de cada argumento.
// ✔️ 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
Si tiene una definición de función larga, coloque los parámetros en nuevas líneas y aplique sangría para que coincidan con el nivel de sangría del parámetro posterior.
// ✔️ 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
Esto también se aplica a los miembros, constructores y parámetros que usan tuplas:
// ✔️ 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
Si se consultan los parámetros, coloque el = carácter junto con cualquier tipo de valor devuelto en una nueva línea:
// ✔️ 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
Se trata de una manera de evitar líneas demasiado largas (en caso de que el tipo de valor devuelto tenga un nombre largo) y menos daños en la línea al agregar parámetros.
Dar formato a declaraciones de operadores
Opcionalmente, use un espacio en blanco para rodear una definición de operador:
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
Para cualquier operador personalizado que comience por y que tenga más de un carácter, debe agregar un espacio en blanco al principio de la definición para evitar una * ambigüedad del compilador. Por este problema, se recomienda rodear simplemente las definiciones de todos los operadores con un único carácter de espacio en blanco.
Dar formato a las declaraciones de registros
Para las declaraciones de registros, aplique sangría a la definición de tipo en cuatro espacios, inicie la lista de campos en la misma línea y alinee los { miembros con el { token:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
member x.ZipAndCity = $"{x.Zip} {x.City}"
No coloque al final de la línea de declaración de tipo y no { use with / end para los miembros, que son redundantes.
// ❌ 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
Cuando se agrega documentación XML para los campos de registro, es normal aplicar sangría y agregar espacios en blanco:
// ✔️ 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}"
Es preferible colocar el token de apertura en una nueva línea y el token de cierre en una nueva línea si se declaran implementaciones de interfaz o miembros en el registro:
// ✔️ 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}"
type MyRecord =
{
/// The record field
SomeField: int
}
interface IMyInterface
Formato de declaraciones de unión discriminadas
Para las declaraciones de unión discriminada, aplique | una sangría en la definición de tipo por cuatro espacios:
// ✔️ 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
Cuando hay una única unión corta, puede omitir el | inicial.
// ✔️ OK
type Address = Address of string
Para una unión más larga o de varias líneas, mantenga y coloque cada campo de unión en una nueva línea, con la separación | al final de cada * línea.
// ✔️ 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
Cuando se agregan comentarios de documentación, use una línea vacía antes de cada /// comentario.
// ✔️ 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
Formato de declaraciones literales
Los literales de F# que usan el atributo deben colocar el atributo en su propia línea y usar la nomenclatura Literal PascalCase:
// ✔️ OK
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Evite colocar el atributo en la misma línea que el valor.
Formato de declaraciones de módulo
El código de un módulo local debe tener sangría en relación con el módulo, pero no se debe aplicar sangría al código de un módulo de nivel superior. No es necesario aplicar sangría a los elementos de espacio de nombres.
// ✔️ 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
Dar formato a las declaraciones do
En declaraciones de tipos, declaraciones de módulo y expresiones de cálculo, a veces se requiere el uso de o para las do do! operaciones de efecto secundario.
Cuando abarcan varias líneas, use la sangría y una nueva línea para mantener la sangría coherente con let / let! . Este es un ejemplo de uso do de en una clase:
// ✔️ 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
Este es un ejemplo con el uso de dos espacios de sangría (porque, por coincidencia, no hay ninguna diferencia entre los enfoques al usar cuatro espacios do! do! de sangría):
// ✔️ 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
}
Aplicar formato a las operaciones de expresión de cálculo
Al crear operaciones personalizadas para expresiones de cálculo,se recomienda usar la nomenclatura 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
}
En última instancia, el dominio que se está modelado debe impulsar la convención de nomenclatura. Si es idiomático usar una convención diferente, se debe usar esa convención en su lugar.
Tipos de formato y anotaciones de tipo
En esta sección se analizan los tipos de formato y las anotaciones de tipo. Esto incluye el formato de los archivos de firma con la .fsi extensión .
Para los tipos, se prefiere la sintaxis de prefijo para genéricos ( Foo<T> ), con algunas excepciones específicas.
F# permite tanto el estilo de postfijo de escritura de tipos genéricos (por ejemplo, ) como el estilo de int list prefijo (por ejemplo, list<int> ).
El estilo de postfijo solo se puede usar con un único argumento de tipo.
Siempre se prefiere el estilo de .NET, excepto cinco tipos específicos:
- Para las listas de F#, use el formato de postfijo:
int listen lugar delist<int>. - Para las opciones de F#, use el formato de postfijo:
int optionen lugar deoption<int>. - Para las opciones de valor de F#, use el formato de postfijo:
int voptionen lugar devoption<int>. - Para las matrices de F#, use el nombre sintáctico
int[]en lugar de oint arrayarray<int>. - Para celdas de referencia, use
int refen lugar de oref<int>Ref<int>.
Para todos los demás tipos, use el formulario de prefijo.
Aplicar formato a los tipos de función
Al definir la firma de una función, use un espacio en blanco alrededor del -> símbolo:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Anotaciones de tipo de argumento y valor de formato
Al definir valores o argumentos con anotaciones de tipo, use un espacio en blanco después del : símbolo, pero no antes:
// ✔️ 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
Aplicar formato a las anotaciones de tipo de valor devuelto
En las anotaciones de tipo de valor devuelto de función o miembro, use espacios en blanco antes y después del : símbolo:
// ✔️ 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
Aplicar formato a tipos en firmas
Al escribir tipos de función completos en firmas, a veces es necesario dividir los argumentos en varias líneas. Para una función tupled, los argumentos se separan por * , colocados al final de cada línea. Se aplica sangría al tipo de valor devuelto. Por ejemplo, considere una función con la siguiente implementación:
let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
En el archivo de firma correspondiente (extensión), la función puede tener el formato siguiente cuando se requiere formato de .fsi varias líneas:
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int
-> int list
Del mismo modo, considere una función consultada:
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
En el archivo de firma correspondiente, -> se colocan al principio de cada línea:
// ✔️ OK
val SampleCurriedFunction:
arg1: string
-> arg2: string
-> arg3: int
-> arg4: int
-> int list
Del mismo modo, considere una función que toma una combinación de argumentos curried y tupled:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
En el archivo de firma correspondiente, -> se colocan al final de cada argumento excepto el último:
// ✔️ 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
Aplicar formato a argumentos y restricciones de tipo genérico explícito
Las instrucciones siguientes se aplican a las definiciones de función, las definiciones de miembro y las definiciones de tipo.
Mantenga los argumentos de tipo genérico y las restricciones en una sola línea si no es demasiado largo:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Si tanto los argumentos o restricciones de tipo genérico como los parámetros de función no caben, pero los parámetros o restricciones de tipo por sí solos lo hacen, coloque los parámetros en nuevas líneas:
// ✔️ OK
let f<'T1, 'T2 when 'T1 : equality and 'T2 : comparison>
param
=
// function body
Si los parámetros o restricciones de tipo son demasiado largos, puede interrumpirlos y alinearlos como se muestra a continuación. Mantenga la lista de parámetros de tipo en la misma línea que la función, independientemente de su longitud. Para las restricciones, coloque en la primera línea y mantenga cada restricción en when una sola línea independientemente de su longitud. Coloque > al final de la última línea. Aplicar sangría a las restricciones en un nivel.
// ✔️ 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 los parámetros o restricciones de tipo se desglosan, pero no hay parámetros de función normales, coloque en = una nueva línea independientemente de lo siguiente:
// ✔️ 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
Atributos de formato
Los atributos se colocan encima de una construcción:
// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...
// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x
// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
Deben ir después de cualquier documentación XML:
// ✔️ OK
/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x
Aplicar formato a atributos en parámetros
Los atributos también se pueden colocar en parámetros. En este caso, coloque en la misma línea que el parámetro y antes del nombre:
// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)
Aplicar formato a varios atributos
Cuando se aplican varios atributos a una construcción que no es un parámetro, se deben colocar de forma que haya un atributo por línea:
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
Cuando se aplican a un parámetro, deben estar en la misma línea y separados por un ; separador.