Linee guida per la formattazione del codice (F#)
In questo argomento vengono riepilogate le linee guida relative al rientro del codice per F#. Poiché il linguaggio F# è sensibile alle interruzioni di riga e al rientro, la corretta formattazione del codice non è solo una questione di leggibilità, estetica o di standardizzazione del codice, ma è necessario formattare il codice correttamente per consentirne una corretta compilazione.
Regole generali per il rientro
Quando è necessario il rientro, è necessario utilizzare spazi e non tabulazioni. È necessario utilizzare almeno uno spazio. L'organizzazione può creare standard di codifica per specificare il numero di spazi da utilizzare per il rientro. In genere vengono utilizzati tre o quattro spazi di rientro a ogni livello in cui questo è presente. È possibile configurare Visual Studio in modo da rispettare gli standard definiti dall'organizzazione per il rientro, modificando le opzioni nella finestra di dialogo Opzioni, disponibile nel menu Strumenti. Nel nodo Editor di testo espandere F# e quindi fare clic su Tabulazioni. Per una descrizione delle opzioni disponibili, vedere Tabulazioni, Tutti i linguaggi, Editor di testo, finestra di dialogo Opzioni.
In generale, quando il codice viene analizzato dal compilatore, viene mantenuto uno stack interno che indica il livello corrente di annidamento. Quando nel codice è impostato un rientro, viene creato un nuovo livello di annidamento o viene inserito in questo stack interno. Quando un costrutto termina, il livello viene prelevato. Il rientro rappresenta un modo per segnalare la fine di un livello e prelevare lo stack interno, ma anche determinati token comportano il prelievo del livello, ad esempio la parola chiave end o una parentesi o parentesi graffa chiusa.
Al codice in un costrutto su più righe, ad esempio una definizione di tipo, una definizione di funzione, un costrutto try...with e i costrutti di ciclo, deve essere applicato un rientro rispetto alla riga di apertura del costrutto. La prima riga a cui viene applicato un rientro stabilisce una posizione di colonna per il codice successivo nello stesso costrutto. Il livello di rientro viene detto contesto. La posizione di colonna imposta una colonna minima, detta riga di offside, per le righe successive di codice nello stesso contesto. Quando viene rilevata una riga di codice con un rientro minore rispetto a questa posizione di colonna stabilita, il compilatore presuppone che il contesto sia terminato e che il codice si trovi al successivo livello superiore, nel contesto precedente. Il termine offside viene utilizzato per descrivere la condizione in cui una riga di codice attiva la fine di un costrutto in quanto il rientro non è sufficientemente grande. In altre parole, il codice a sinistra di una riga di offside viene considerato offside. Se il rientro nel codice è impostato correttamente, la regola di offside consente di definire la fine dei costrutti. Se si utilizza il rientro in modo non appropriato, una condizione di offside può comportare la generazione di un avviso da parte del compilatore o un'interpretazione errata del codice.
Le righe di offside sono determinate come indicato di seguito.
Un token = associato a un elemento let introduce una riga di offside in corrispondenza della colonna del primo token dopo il segno =.
In un'espressione if...then...else la posizione di colonna del primo token dopo la parola chiave then o la parola chiave else introduce una riga di offside.
In un'espressione try...with il primo token dopo try introduce una riga di offside.
Nell'espressione match il primo token dopo with e il primo token dopo ogni elemento -> introducono righe di offside.
Il primo token dopo with in un'estensione di tipo introduce una riga di offside.
Il primo token dopo una parentesi o parentesi graffa aperta o dopo la parola chiave begin introduce una riga di offside.
Il primo carattere nelle parole chiave let, if e module introduce righe di offside.
Negli esempi di codice seguenti vengono illustrate le regole per il rientro. In questo caso, per le istruzioni di stampa è necessario utilizzare il rientro affinché vengano associate al contesto appropriato. Ogni volta che il rientro viene spostato, il contesto viene prelevato e si torna al contesto precedente. Alla fine di ogni iterazione viene pertanto visualizzato uno spazio. "Done!"viene visualizzato una sola volta, in quanto il rientro di offside ne determina l'esclusione dal ciclo. La stampa della stringa "Top-level context" non fa parte della funzione. La stringa viene pertanto stampata inizialmente, durante l'inizializzazione statica, prima che venga chiamata la funzione.
let printList list1 =
for elem in list1 do
if elem > 0 then
printf "%d" elem
elif elem = 0 then
printf "Zero"
else
printf "(Negative number)"
printf " "
printfn "Done!"
printfn "Top-level context."
printList [-1;0;1;2;3]
L'output è indicato di seguito.
Top-level context
(Negative number) Zero 1 2 3 Done!
Quando si interrompono righe lunghe, per la continuazione della riga deve essere impostato un rientro maggiore rispetto a quello del costrutto in cui è inclusa. Per gli argomenti di una funzione è ad esempio necessario impostare un rientro maggiore rispetto al primo carattere del nome della funzione, come illustrato nel codice seguente.
let myFunction1 a b = a + b
let myFunction2(a, b) = a + b
let someFunction param1 param2 =
let result = myFunction1 param1
param2
result * 100
let someOtherFunction param1 param2 =
let result = myFunction2(param1,
param2)
result * 100
Queste regole prevedono alcune eccezioni, descritte nella sezione successiva.
Rientro nei moduli
Il rientro nel codice in un modulo locale deve essere impostato in relazione al modulo, ma al codice in un modulo di primo livello non è necessario applicare un rientro. Per gli elementi dello spazio dei nomi non è necessario un rientro.
Questo aspetto è illustrato negli esempi di codice seguenti.
// Program1.fs
// A is a top-level module.
module A
let function1 a b = a - b * b
// Program2.fs
// 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
Per ulteriori informazioni, vedere Moduli (F#).
Eccezioni alle regole di base per il rientro
La regola generale, come descritto nella sezione precedente, prevede che al codice nei costrutti su più righe venga applicato un rientro in relazione alla prima riga del costrutto e che la fine del costrutto sia determinata dalla prima riga di offside. Un'eccezione alla regola relativa alla fine dei contesti riguarda il fatto che alcuni costrutti, ad esempio l'espressione try...with, l'espressione if...then...else e l'utilizzo della sintassi and per la dichiarazione di funzioni o tipi ricorsivi reciproci, sono costituiti da più parti. Il rientro per le parti finali, ad esempio then e else in un'espressione if...then...else viene impostato allo stesso livello del token di inizio dell'espressione, ma anziché indicare la fine del contesto, il rientro rappresenta la parte successiva dello stesso contesto. Un'espressione if...then...else può essere pertanto scritta come indicato nell'esempio di codice seguente.
let abs1 x =
if (x >= 0)
then
x
else
-x
L'eccezione alla regola di offside si applica solo alle parole chiave then e else. Di conseguenza, sebbene impostare un ulteriore rientro per then e else non sia un errore, la mancanza di un rientro nelle righe di codice in un blocco then comporta la generazione di un avviso. Questo aspetto è illustrato nelle righe di codice seguenti.
// The following code does not produce a warning.
let abs2 x =
if (x >= 0)
then
x
else
-x
// The following code is not indented properly and produces a warning.
let abs3 x =
if (x >= 0)
then
x
else
-x
Per il codice in un blocco else, si applica una regola speciale aggiuntiva. L'avviso nell'esempio precedente viene generato solo nel codice nel blocco then e non nel blocco else. In questo modo, è possibile scrivere codice tramite cui vengono controllate diverse condizioni all'inizio di una funzione senza forzare il rientro per il resto del codice per la funzione, che potrebbe trovarsi in un blocco else. È pertanto possibile scrivere il codice seguente senza che venga generato un avviso.
let abs4 x =
if (x >= 0) then x else
-x
Un'altra eccezione alla regola che prevede la fine dei contesti quando a una riga non viene applicato un rientro corrispondente alla riga precedente riguarda gli operatori infissi, ad esempio + e |>. Le righe che iniziano con operatori infissi possono iniziare (1 + oplength) colonne prima della posizione normale senza attivare la fine del contesto, dove oplength indica il numero di caratteri che costituiscono l'operatore. In questo modo, il primo token dopo l'operatore è allineato alla riga precedente.
Nel codice seguente, ad esempio, il simbolo + può rientrare di due colonne in meno rispetto alla riga precedente.
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2
+ arg3 + arg4
Sebbene il rientro aumenti in genere con l'aumentare del livello di annidamento, per alcuni costrutti il compilatore consente di reimpostare il rientro a una posizione di colonna inferiore.
I costrutti che permettono la reimpostazione della posizione di colonna sono i seguenti:
Corpi di funzioni anonime. Nel codice seguente l'espressione di stampa inizia da una posizione di colonna più a sinistra rispetto alla parola chiave fun. La riga non deve tuttavia iniziare in corrispondenza di una colonna a sinistra dell'inizio del livello di rientro precedente (ovvero, a sinistra di L in List).
let printListWithOffset a list1 = List.iter (fun elem -> printfn "%d" (a + elem)) list1
Costrutti racchiusi tra parentesi o tra elementi begin e end in un blocco then o else di un'espressione if...then...else, a condizione che il rientro non sia minore della posizione di colonna della parola chiave if. Questa eccezione consente di utilizzare uno stile di codifica in cui una parentesi aperta o un elemento begin viene utilizzato alla fine di una riga dopo then o else.
Corpi di moduli, classi, interfacce e strutture delimitati da begin...end, {...}, class...end o interface...end. Questa eccezione consente di utilizzare uno stile in cui la parola chiave di apertura di una definizione del tipo può trovarsi sulla stessa riga del nome del tipo senza forzare un rientro per l'intero corpo maggiore rispetto a quello della parola chiave di apertura.
type IMyInterface = interface abstract Function1: int -> int end