Instrucciones de formato de código de F#
En este artículo se ofrecen instrucciones de cómo aplicar formato al código para que el código de F#:
- Sea más legible
- Vaya de acuerdo con las convenciones aplicadas por las herramientas de formato de Visual Studio Code y otros editores
- Sea similar a otro código en línea
Consulte también Convenciones de código y Directrices de diseño de componentes, que también abarca las convenciones de nomenclatura.
Aplicación automática de formato al código
El formateador de código Fantomas es la herramienta estándar de la comunidad de F# para la aplicación automática de formato al 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 un archivo de configuración acordado para el formateador de código insertado en el repositorio del equipo.
Reglas generales para el formato
F# usa numerosos espacios en blanco de forma predeterminada y es sensible a estos. Las instrucciones siguientes están pensadas para proporcionar una guía sobre cómo sortear algunos obstáculos que esta característica puede imponer.
Uso de espacios, no tabulaciones
Cuando se requiera 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 o comentario de cadena.
Uso de sangría de forma coherente
Al aplicar sangría, se requiere al menos un espacio. Su organización puede crear estándares de creación de código para especificar el número de espacios que se van a usar para la sangría; lo habitual son dos, tres o cuatro espacios de sangría en cada nivel donde se produce la sangría.
Se recomiendan cuatro espacios por sangría.
Dicho esto, la sangría de los programas es una cuestión subjetiva. Se aceptan variaciones, pero la primera regla que debe seguir es la coherencia de la sangría. Elija un estilo de sangría aceptado generalmente y úselo sistemáticamente en todo el código base.
Evite formato que sea sensible a la longitud de los nombres.
Intente evitar sangría y alineación que sean sensibles 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 evitarlo son:
- El código importante se desplaza a la derecha.
- Queda menos ancho para el código real.
- Cambiar el nombre puede romper la alineación.
Evitar espacios en blanco extraños
Evite espacios en blanco extraños 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 )
Aplicación de formato a los comentarios
Se prefieren varios comentarios de barra diagonal doble 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.
*)
En los comentarios debe ir en mayúscula la primera letra y deben ser frases u oraciones bien formadas.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Para información sobre el formato de los comentarios de documentación XML, consulte "Declaraciones de formato" a continuación.
Aplicación de formato a expresiones
En esta sección se describen la aplicación de formato a expresiones de diferentes tipos.
Aplicación de formato a expresiones de cadena
Los literales de cadena y las cadenas interpoladas solo se pueden dejar en una línea, con independencia de lo larga que sea la línea.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
No se recomienda usar expresiones interpoladas de varias líneas. En su lugar, enlace el resultado de la expresión a un valor y úselo en la cadena interpolada.
Aplicación de formato a expresiones de tupla
Una creación de instancias de tupla debe estar entre paréntesis y las comas delimitadoras dentro de la instancia 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, se prefieren las instancias de tupla entre paréntesis, pero cuando se usan tuplas para la coincidencia de patrones o un valor devuelto, se considera correcto evitar los paréntesis.
Aplicación de formato a expresiones de aplicación
Al dar formato a una aplicación de método o función, los argumentos se proporcionan en la misma línea cuando el ancho de línea lo permite:
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Omita paréntesis a menos que los argumentos los requieran:
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)
// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)
No omita espacios al invocar con varios argumentos currificados:
// ✔️ 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)
En las convenciones de formato predeterminadas, se agrega un espacio al aplicar funciones en minúscula a argumentos entre paréntesis o en tupla (incluso cuando se usa un único argumento):
// ✔️ 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 ningún espacio al aplicar métodos en mayúsculas a argumentos en tupla. Esto se debe a que a menudo se usan con 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 por motivos de legibilidad o porque la lista de argumentos o los nombres de argumento son demasiado largos. En ese caso, aplique sangría de 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 tome un único argumento en tupla 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éngalos en una sola 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 sangría de un nivel, en lugar de aplicar sangría a la paréntesis izquierdos.
// ✔️ 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 aunque solo haya 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" ]
)
Aplicación de formato a expresiones de canalización
Al usar 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
Aplicación de formato a 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 de 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 la expresión lambda de coincidencia de una manera similar.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Cuando haya muchos argumentos iniciales o de varias líneas antes de la expresión lambda, aplique sangría de un nivel a todos los argumentos.
// ✔️ 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 es de varias líneas de longitud, debe considerar la posibilidad de refactorizarlo 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}")
En caso de que los argumentos de una expresión lambda no se ajusten a una sola línea o sean multilínea, colóquelos en la siguiente línea, aplicando sangría de un nivel.
// ✔️ 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 ()
Aplicación de 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
No rodear un operador binario -
, cuando se combina con determinadas opciones de formato, podría dar lugar a que se interpretara como un operador -
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 con espacios. Las expresiones de infijo son aceptables 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
Esta regla también se aplica a unidades de medidas en anotaciones de tipos y 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)> }
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 tienden 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
Aplicación de formato a expresiones de operador de intervalo
Agregue solo espacios alrededor de ..
cuando todas las expresiones no sean atómicas.
Los enteros y los identificadores de palabra única 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 la segmentación:
// ✔️ OK
arr[0..10]
list[..^1]
Aplicación de formato a expresiones condicionales (if)
La sangría de las condicionales depende del tamaño y la complejidad de las expresiones que las componen. Escríbalas en una línea cuando:
cond
,e1
, ye2
son cortos.e1
ye2
no sean expresionesif/then/else
por sí mismas.
// ✔️ OK
if cond then e1 else e2
Si la expresión else no está presente, se recomienda no escribir nunca toda la expresión en una línea. El motivo es diferenciar el código imperativo del funcional.
// ✔️ OK
if a then
()
// ❌ Not OK, code formatters will reformat to the above by default
if a then ()
Si alguna de las expresiones es de varias líneas, cada rama condicional debe ser de varias líneas.
// ✔️ OK
if cond then
let e1 = something()
e1
else
e2
// ❌ Not OK
if cond then
let e1 = something()
e1
else e2
Se aplica sangría a varios condicionales con elif
y else
en el mismo ámbito que if
cuando siguen las reglas de las expresiones if/then/else
de una línea.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Si alguna de las condiciones o expresiones es de varias líneas, toda la expresión if/then/else
es de varias líneas:
// ✔️ 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 una condición es de varias líneas o supera la tolerancia predeterminada de la línea única, la expresión de condición debe usar una sangría y una nueva línea.
La palabra clave if
y then
debe alinearse al encapsular la expresión de condición larga.
// ✔️ 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
Sin embargo, es mejor el estilo de refactorizar 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
Aplicación de formato a expresiones de mayúsculas y minúsculas de unión
La aplicación de mayúsculas y minúsculas de unión discriminadas sigue las mismas reglas que las aplicaciones de método y función. Es decir, dado que el nombre está en mayúsculas, los formateadores de código quitarán un espacio delante 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)
)
Aplicación de formato a expresiones de lista y matriz
Escriba x :: l
con espacios alrededor del operador ::
(::
es un operador infijo, de ahí que esté rodeado por espacios).
En la lista y las matrices declaradas en una sola línea debe haber 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 sucede con los registros, declarar los corchetes de apertura y cierre en su propia línea hará que sea más fácil desplazar el código e introducirlo en las funciones:
// ✔️ 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 una expresión de lista o matriz es el lado derecho de un enlace, es posible que prefiera usar el estilo 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 |]
|]
Sin embargo, cuando una expresión de lista o matriz no es el lado derecho de un enlace, como cuando está dentro de otra lista o matriz, si esa expresión interna necesita abarcar varias líneas, los corchetes deben ir en sus propias líneas:
// ✔️ 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
] ]
La misma regla se aplica a los tipos de registro dentro de matrices o listas:
// ✔️ 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
} ]
Al generar matrices y listas mediante programación, se prefiere ->
sobre do ... yield
cuando siempre se genera un 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 yield
en situaciones en las que los datos se pueden generar condicionalmente o pueda haber expresiones consecutivas que se vayan a evaluar. Es preferible omitir estas palabras clave yield
a menos que la compilación la realice 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 contribuir a la legibilidad. Estos casos, aunque subjetivas, deben tenerse en cuenta.
Aplicación de formato a expresiones de registro
Los registros cortos se pueden escribir en una sola línea:
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
Los registros que son más largos deben usar nuevas líneas para las etiquetas:
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Estilos de formato de corchetes de varias líneas
En el caso de registros que abarcan varias líneas, hay tres estilos de formato usados habitualmente: Cramped
, Aligned
y Stroustrup
. El estilo Cramped
ha sido el estilo predeterminado en el código de F#, ya que tiende a favorecer los estilos que permiten al compilador analizar fácilmente el código. Los estilos Aligned
y Stroustrup
permiten una reordenación más sencilla de los miembros, lo que conduce a que el código pueda ser más fácil de refactorizar, con el inconveniente de que en ciertas situaciones puede que haga falta un código ligeramente más detallado.
Cramped
: el estándar histórico y el formato de registro de F# predeterminado. Los corchetes de apertura van en la misma línea que el primer miembro y el corchete de cierre en la misma línea que el último miembro.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Aligned
: cada corchete tiene su propia línea, alineada en la misma columna.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }
Stroustrup
: el corchete de apertura va en la misma línea que el enlace y el de cierre tiene su propia línea.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Se aplican las mismas reglas de estilo de formato a los elementos de lista y matriz.
Aplicación de formato a 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 usarse en nuevas líneas, y el formato debe basarse en una de las convenciones mencionadas anteriormente:
// ✔️ 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 = ""
}
}
Nota: Si usa el estilo Stroustrup
para las expresiones de copia y actualización, debe aplicar sangría a los miembros más allá del nombre del registro copiado:
// ✔️ 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"
}
Aplicación de formato a la coincidencia de patrones
Use una |
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 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 la expresión situada a la derecha de la flecha de coincidencia de patrones es demasiado grande, aplíquele sangría un paso desde match
/|
.
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
De forma similar a las condiciones if largas, si una expresión de coincidencia tiene varias líneas o supera la tolerancia predeterminada de la línea única, la expresión de coincidencia debe usar una sangría y una nueva línea.
La palabra clave match
y with
debe alinearse al encapsular la expresión de coincidencia larga.
// ✔️ OK, but better to refactor, see below
match
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
with
| X y -> y
| _ -> 0
Sin embargo, es mejor el estilo de refactorizar condiciones de coincidencia largas en un enlace let o una función independiente:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
match performAction with
| X y -> y
| _ -> 0
Debe evitarse alinear las flechas de una coincidencia de patrones.
// ✔️ 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
Se debe aplica sangría de un nivel a la coincidencia de patrones introducida mediante la palabra clave function
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)
El uso de function
en funciones definidas por let
o let rec
debe evitarse en general en favor de 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
Aplicación de formato a expresiones try/with
Se debe aplica el mismo nivel de sangría a la coincidencia de patrones del tipo de excepción que a 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 una |
a cada cláusula, excepto cuando solo haya una cláusula:
// ✔️ 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
Aplicación de formato a argumentos con nombre
Los argumentos con nombre deben tener espacios que rodean la =
:
// ✔️ 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)
Cuando la coincidencia de patrones usa uniones discriminadas, los patrones con nombre reciben 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, 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
Aplicación de formato a expresiones de mutación
Normalmente, las expresiones de mutación location <- expr
tienen el formato de una línea.
Si se requiere un 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
Aplicación de formato a expresiones de objeto
Los miembros de la expresión de objeto deben estar alineados con member
, con un nivel de sangría.
// ✔️ 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) }
También puede preferir usar el estilo 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)
}
A las definiciones de tipo vacías se les puede asignar el formato de que estén en una sola línea:
type AnEmptyType = class end
Independientemente del ancho de página elegido, = class end
siempre debe estar en la misma línea.
Aplicación de 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 ]
Lo mismo 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 ]
Aplicación de formato a expresiones entre comillas
Los símbolos delimitadores (<@
, @>
, <@@
, @@>
) deben colocarse en líneas distintas si la expresión entre comillas es una expresión de varias líneas.
// ✔️ OK
<@
let f x = x + 10
f 20
@>
// ❌ Not OK
<@ let f x = x + 10
f 20
@>
En las expresiones de una sola línea, los símbolos delimitadores deben colocarse en la misma línea que la propia expresión.
// ✔️ OK
<@ 1 + 1 @>
// ❌ Not OK
<@
1 + 1
@>
Aplicación de formato a expresiones encadenadas
Cuando las expresiones encadenadas (las aplicaciones de función entrelazadas con .
) sean largas, coloque cada invocación de aplicación en la línea siguiente.
Aplique un nivel de sangría a los vínculos posteriores de la cadena después del vínculo inicial.
// ✔️ OK
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
// ✔️ OK
Cli
.Wrap("git")
.WithArguments(arguments)
.WithWorkingDirectory(__SOURCE_DIRECTORY__)
.ExecuteBufferedAsync()
.Task
El vínculo inicial se puede componer de varios vínculos si son identificadores simples. Por ejemplo, la adición de un espacio de nombres completo.
// ✔️ OK
Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
Los vínculos posteriores también deben contener identificadores 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))
Cuando los argumentos dentro de una aplicación de función no quepan en el resto de la línea, coloque cada argumento en la línea siguiente.
// ✔️ 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
Los argumentos lambda dentro de una aplicación de función deben iniciarse en la misma línea que el corchete de apertura ((
).
// ✔️ OK
builder
.WithEnvironment()
.WithLogger(fun loggerConfiguration ->
// ...
())
// ❌ Not OK, formatting tools will reformat to the above
builder
.WithEnvironment()
.WithLogger(
fun loggerConfiguration ->
// ...
())
Aplicación de formato a declaraciones
En esta sección se describe la aplicación de formato a declaraciones de diferentes tipos.
Adición de líneas en blanco entre declaraciones
Separe las definiciones de clase y función 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 documentación XML, agregue una línea en blanco delante.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Aplicación de formato a declaraciones let y member
Cuando se aplica formato a declaraciones let
y member
, normalmente el lado derecho de un enlace va en una línea o (si es demasiado largo) pasa a una nueva línea con sangría de un nivel.
Por ejemplo, los siguientes ejemplos son aceptables:
// ✔️ 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
Estos no lo son:
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
let d = while f do
printfn "%A" x
Las instancias de tipo de registro también pueden colocar los corchetes en sus propias líneas:
// ✔️ OK
let bilbo =
{
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Puede que también prefiera usar el estilo Stroustrup
, con la apertura {
en la misma línea que el nombre del enlace:
// ✔️ OK
let bilbo = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Separe los miembros con una sola línea en blanco y documente 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 montón de comentarios relacionados (por ejemplo, un conjunto de implementaciones ficticias). Use líneas en blanco en funciones, con moderación, para indicar secciones lógicas.
Aplicación de formato a argumentos de miembro y función
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 aplíqueles sangría para que coincidan con el nivel de sangría del parámetro subsiguiente.
// ✔️ 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 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
// ✔️ OK
type TypeWithLongSecondaryConstructor () =
new
(
aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse
) =
// ... the body of the constructor follows
Si los parámetros están currificados, 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
Esta es una manera de evitar líneas demasiado largas (en caso de que el tipo de valor devuelto tenga un nombre largo) y tener menos daños en las líneas al agregar parámetros.
Aplicación de formato a declaraciones de operador
Opcionalmente, use espacios en blanco para rodear una definición de operador:
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
Para los operadores personalizados que comiencen por *
y tengan más de un carácter, debe agregar un espacio en blanco al principio de la definición para evitar la ambigüedad del compilador. Por este motivo, se recomienda simplemente rodear las definiciones de todos los operadores con un solo carácter de espacio en blanco.
Aplicación de formato a declaraciones de registro
Para las declaraciones de registro, aplique cuatro espacios de sangría al carácter {
en la definición de tipo, inicie la lista de etiquetas en la misma línea y alinee los miembros, si los hay, con el token {
:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
También es habitual preferir colocar corchetes en su propia línea y aplicar cuatro espacios más de sangría a las etiquetas:
// ✔️ OK
type PostalAddress =
{
Address: string
City: string
Zip: string
}
También puede colocar el carácter {
al final de la primera línea de la definición de tipo (estilo Stroustrup
):
// ✔️ OK
type PostalAddress = {
Address: string
City: string
Zip: string
}
Si se necesitan miembros adicionales, no use with
/end
siempre que sea posible:
// ✔️ 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
La excepción a esta regla de estilo es si aplica formato a los registros según el estilo Stroustrup
. En esta situación, debido a las reglas del compilador, se requiere la palabra clave with
si quiere implementar una interfaz o agregar más miembros:
// ✔️ 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}"
Cuando se agrega documentación XML a los campos de registro, se prefiere el estilo Aligned
o Stroustrup
, y se deben agregar espacios en blanco adicionales entre los miembros:
// ❌ 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}"
Es preferible colocar el token de apertura en una nueva línea y el token de cierre en otra si declara 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}"
// ✔️ OK
type MyRecord =
{
/// The record field
SomeField: int
}
interface IMyInterface
Estas mismas reglas se aplican a los alias de tipo de registro anónimos.
Aplicación de formato a declaraciones de unión discriminadas
Para declaraciones de unión discriminadas, aplique cuatro espacios de sangría a |
en la definición de tipo:
// ✔️ 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 sola unión corta, puede omitir el signo |
inicial.
// ✔️ OK
type Address = Address of string
En el caso de una unión de varias líneas o más larga, mantenga el signo |
y coloque cada campo de unión en una nueva línea, con la *
de 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 agreguen comentarios de documentación, use una línea vacía delante 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
Aplicación de formato a declaraciones de literal
Los literales de F# que usan el atributo Literal
deben colocar el atributo en su propia línea y usar la nomenclatura 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.
Aplicación de formato a declaraciones de módulo
Se debe aplicar sangría al código de un módulo local 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
Aplicación de formato a declaraciones do
En declaraciones de tipo, declaraciones de módulo y expresiones de cálculo, a veces es necesario el uso de do
o do!
para las operaciones de efecto colateral.
Cuando estas 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 de do
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 do!
usando dos espacios de sangría (porque con do!
no hay ninguna diferencia entre los enfoques al usar cuatro espacios 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
}
Aplicación de formato a 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 va a modelar debe determinar la convención de nomenclatura. Si es idiomático usar una convención diferente, esa convención debe usarse en su lugar.
Si el valor devuelto de una expresión es una expresión de cálculo, es preferible colocar el nombre de palabra clave de la expresión de cálculo en su propia línea:
// ✔️ OK
let foo () =
async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
También puede que prefiera colocar la expresión de cálculo en la misma línea que el nombre del enlace:
// ✔️ OK
let foo () = async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Sea cual sea su preferencia, su objetivo debe ser mantener la coherencia en todo el código base. Puede que los formateadores le permitan especificar esta preferencia para mantener la coherencia.
Aplicación de formato a tipos y anotaciones de tipo
En esta sección se describen los tipos de formato y las anotaciones de tipo. Se incluye el formato de los archivos de firma con la extensión .fsi
.
En el caso de los tipos, se prefiere la sintaxis de prefijo para los genéricos (Foo<T>
), con algunas excepciones específicas.
F# permite el estilo de postfijo de escribir tipos genéricos (por ejemplo, int list
) y el estilo de 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 en el caso de cinco tipos específicos:
- Para las listas de F#, use la forma de postfijo:
int list
en lugar delist<int>
. - Para las opciones de F#, use la forma de postfijo:
int option
en lugar deoption<int>
. - Para las opciones de valor de F#, use la forma de postfijo:
int voption
en lugar devoption<int>
. - Para las matrices F#, use el formato postfijo:
int array
en lugar dearray<int>
oint[]
. - Para las celdas de referencia, use
int ref
en lugar deref<int>
oRef<int>
.
Para todos los demás tipos, use la forma de prefijo.
Aplicación de formato a tipos de función
Al definir la firma de una función, use espacios en blanco alrededor del símbolo ->
:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Aplicación de formato a anotaciones de tipo de argumento y valor
Al definir valores o argumentos con anotaciones de tipo, use espacios en blanco después del símbolo :
, pero no delante:
// ✔️ 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
Aplicación de formato a las anotaciones de tipo multilínea
Cuando una anotación de tipo es larga o multilínea, colóquela en la siguiente línea, aplicando sangría en un nivel.
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
En el caso de los tipos de registros anónimos insertados, también puede usar el estilo Stroustrup
:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Aplicación de formato a anotaciones de tipo de valor devuelto
En las anotaciones de tipo de valor devuelto de miembro o función, use espacios en blanco delante y detrá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
Aplicación de 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. Siempre se aplica sangría al tipo de valor devuelto.
En una función en tupla, los argumentos están separados por el signo *
, colocado al final de cada línea.
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 .fsi
), se puede aplicar formato a la función de la siguiente manera cuando se requiere formato de varias líneas:
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list
Del mismo modo, considere una función currificada:
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
En el archivo de firma correspondiente, los signos ->
se colocan al final 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 currificados y en tupla:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
En el archivo de firma correspondiente, se aplica sangría a los tipos precedidos por una tupla.
// ✔️ 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
Las mismas reglas se aplican a los miembros en las firmas de tipo:
type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string
Aplicación de formato a restricciones y argumentos de tipo genérico explícitos
Las instrucciones siguientes se aplican a las definiciones de función, las definiciones de miembro, las definiciones de tipo y las aplicaciones de función.
Mantenga los argumentos y restricciones de tipo genéricos en una sola línea si no es demasiado larga:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Si no caben los argumentos o restricciones de tipo genérico y las propiedades de función, pero los parámetros y restricciones de tipo sí, 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, divídalos y alinéelos 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. En el caso de las restricciones, coloque when
en la primera línea y mantenga cada restricción en una sola línea sin importar su longitud. Coloque >
al final de la última línea. Aplique un nivel de sangría a las restricciones.
// ✔️ 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 se dividen los parámetros o restricciones de tipo, pero no hay parámetros de función normales, coloque el signo =
en una nueva línea sin tener en cuenta 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
Las mismas reglas se aplican a las aplicaciones de función:
// ✔️ 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
Aplicación de formato a la herencia
Los argumentos del constructor de clase base aparecen en la lista de argumentos en la cláusula inherit
.
Coloque la cláusula inherit
en una nueva línea, aplicando sangría en un nivel.
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)
Cuando el constructor es largo o multilínea, colóquelo en la siguiente línea, aplicando sangría en un nivel.
Aplique formato a este constructor multilínea según las reglas de las aplicaciones de funciones multilínea.
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
""")
Aplicación de formato al constructor principal
En las convenciones de formato predeterminadas, no se agrega ningún espacio entre el nombre de tipo y los paréntesis del constructor 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
Varios constructores
Cuando la cláusula inherit
forma parte de un registro, colóquela en la misma línea si es corta.
Y colóquela en la siguiente línea, aplicando sangría en un nivel, si es larga o multilínea.
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 }
Aplicación de formato a atributos
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
Aplicación de formato a atributos en parámetros
Los atributos también se pueden colocar en parámetros. En este caso, colóquelos en la misma línea que el parámetro y delante 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)
Aplicación de formato a varios atributos
Cuando se apliquen varios atributos a una construcción que no es un parámetro, coloque cada atributo en una línea distinta:
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
Cuando se apliquen a un parámetro, coloque los atributos en la misma línea y sepárelos con un separador ;
.
Agradecimientos
Estas directrices se basan en el documento Guía completa sobre las convenciones de formato de F# de Anh-Dung Phan.