コードのフォーマットに関するガイドライン (F#)

ここでは、F# のコードのインデントに関するガイドラインを示します。 F# 言語では改行やインデントが認識されるため、コードの正しいフォーマットは、単なる読みやすさ、美的外観、コーディング規則に関する懸案事項ではありません。 正しくコンパイルするには、コードのフォーマットが正しいことが必要です。

インデントの一般的な規則

インデントが必要な場合は、タブではなく、空白を使用する必要があります。 少なくとも 1 つの空白が必要です。 インデントに使用する空白の数は、組織でコーディング規則を作成して指定できます。インデントを設定するレベルごとに 3 つか 4 つの空白を使用するのが一般的です。 Visual Studio を組織のインデント規則に合わせて構成するには、[オプション] ダイアログ ボックスのオプションを変更します。このダイアログ ボックスは、[ツール] メニューから開くことができます。 [テキスト エディター] ノードで、[F#] を展開して [タブ] をクリックします。使用できるオプションについては、「[オプション]、[テキスト エディター]、[すべての言語]、[タブ]」を参照してください。

一般に、コンパイラによるコードの解析時には、現在の入れ子のレベルを示す内部スタックが保持されます。 コードにインデントが設定されていると、新しい入れ子のレベルが作成されます。つまり、この内部スタックにプッシュされます。 構成要素が終了すると、レベルがポップされます。 インデントはレベルの終了を示して内部スタックをポップするための 1 つの方法であり、end キーワードや右中かっこまたは右かっこなど、特定のトークンでもレベルがポップされます。

型定義、関数定義、try...with 構成要素、ループ構造など、複数行にわたる構成要素のコードには、構成要素の先頭の行を基準としてインデントを設定する必要があります。 インデントが設定された最初の行によって、同じ構成要素内の後続のコードの列の位置が決まります。 このインデントのレベルはコンテキストと呼ばれます。 列の位置により、同じコンテキスト内の後続のコード行の最小の列 (オフサイド ラインと呼ばれます) が設定されます。 この設定された列の位置よりもインデントが少ないコード行がコンパイラで検出されると、前のコンテキストが終了して次の 1 つ上のレベルのコーディングが開始されたと見なされます。 オフサイドとは、十分なインデントが設定されていないために、コード行で構成要素の終了がトリガーされる状況を指します。 つまり、オフサイド ラインの左にあるコードがオフサイドです。 インデントが正しく設定されたコードでは、構成要素の終了を示すためにオフサイド ルールを利用できます。 インデントの使用が不適切な場合は、オフサイド状態となるため、コンパイラから警告メッセージが発行される場合や、コードが正しく解釈されない場合があります。

オフサイド ラインは次のようにして決まります。

  • let に関連付けられた = トークンがある場合は、= 記号の後の最初のトークンの列にオフサイド ラインが設定されます。

  • if...then...else 式では、then キーワードまたは else キーワードの後の最初のトークンの列の位置にオフサイド ラインが設定されます。

  • try...with 式では、try の後の最初のトークンにオフサイド ラインが設定されます。

  • match 式では、with キーワードの後の最初のトークン、およびそれぞれの -> キーワードの後の最初のトークンにオフサイド ラインが設定されます。

  • 型拡張の with の後の最初のトークンにオフサイド ラインが設定されます。

  • 左中かっこや左かっこの後、または begin の後の最初のトークンにオフサイド ラインが設定されます。

  • let、if、および module の各キーワードの最初の文字にオフサイド ラインが設定されます。

インデント規則について、次のコード例で説明します。 この例の print ステートメントは、インデントに基づいて適切なコンテキストに関連付けられています。 インデントが変わるたびに、コンテキストがポップされ、前のコンテキストに戻ります。 そのため、反復処理が終了するたびに空白が出力されます。"Done!" が出力されるのは 1 回だけですが、これは、オフサイドのインデントによって、"Done!" はループに含まれないことが設定されるためです。 "Top-level context" という文字列の出力は、関数に含まれていません。 そのため、この文字列は、関数が呼び出される前の静的な初期化の段階で最初に出力されます。

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]

出力は次のとおりです。

Top-level context

(Negative number) Zero 1 2 3 Done!

長い行を改行する場合、続きの行では、外側の構成要素よりもインデントを下げる必要があります。 たとえば、次のコードに示すように、関数の引数は関数名の最初の文字よりもインデントを下げる必要があります。

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

これらの規則には例外があります。例外についてはこの後のセクションで説明します。

モジュールのインデント

ローカル モジュールのコードには、モジュールを基準にしたインデントを設定する必要がありますが、最上位のモジュールにインデントを設定する必要はありません。 名前空間の要素にインデントを設定する必要はありません。

この例を次のコード例に示します。

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

詳細については、「モジュール (F#)」を参照してください。

基本的なインデント規則の例外

前のセクションで説明した一般的な規則では、複数行にわたる構成要素のコードには、その構成要素の最初の行のインデントを基準としてインデントを設定する必要があり、構成要素の終了は、最初のオフサイド ラインの出現によって判断されます。 ただし、try...with 式や if...then...else 式、and 構文を使用した相互再帰関数または型の宣言など、複数の部分から構成される一部の構成要素には、コンテキストの終了に関する例外があります。 これらの後の部分 (if...then...else 式の then と else など) には、式の先頭のトークンと同じレベルのインデントを設定しますが、これは、コンテキストの終了を示すのではなく、同じコンテキストの次の部分であることを表します。 したがって、if...then...else 式は次のコード例のように記述できます。

let abs1 x =
    if (x >= 0)
    then
        x
    else
        -x

オフサイド ルールの例外は、then キーワードと else キーワードにのみ適用されます。 そのため、then と else のインデントを下げてもエラーにはなりませんが、then ブロックのコード行にインデントを設定していない場合は警告が生成されます。 この例を次のコード行に示します。

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

else ブロックのコードには、さらに特別な規則が適用されます。 前の例の警告は、then ブロックのコードに対してのみ生成され、else ブロックのコードに対しては生成されません。 そのため、関数のコードの残りの部分 (else ブロックのコードなど) にインデントを設定せずに、さまざまな条件をチェックするコードを関数の先頭に記述することができます。 したがって、次のようなコードを記述しても、警告は生成されません。

let abs4 x =
    if (x >= 0) then x else
    -x

前の行よりもインデントが少ない場合のコンテキストの終了に関するもう 1 つの例外は、+ や |> などの挿入演算子に関するものです。 挿入演算子で始まる行は、コンテキストの終了をトリガーすることなく、通常の位置より (1 + oplength) 列前から開始できます (oplength は演算子の文字数)。 このようにすると、演算子の後の最初のトークンの位置が前の行に揃えられます。

たとえば、次のコードでは、+ 記号のインデントを前の行よりも 2 列手前に設定することができます。

let function1 arg1 arg2 arg3 arg4 =
    arg1 + arg2
  + arg3 + arg4

インデントは、入れ子のレベルが高くなるにつれて増えていくのが普通ですが、いくつかの構成要素では、コンパイラで、より手前の列の位置にインデントをリセットすることができます。

列の位置をリセットできる構成要素は次のとおりです。

  • 匿名関数の本体。 次のコードでは、print 式が、fun キーワードよりも左の列の位置で始まっています。 ただし、この行を、前のインデント レベルの先頭よりも左の列 (つまり、List の L よりも左) で開始することはできません。

    let printListWithOffset a list1 =
        List.iter (fun elem ->
            printfn "%d" (a + elem)) list1
    
  • かっこで囲まれた構成要素、then の begin と end で囲まれた構成要素、または if...then...else 式の else ブロック (インデントが if キーワードの列の位置よりも後に設定されている場合)。 この例外により、左かっこまたは begin を then または else の後の行の末尾に使用するコーディング スタイルに対応できます。

  • begin...end、{...}、class...end、または interface...end で区切られたモジュール、クラス、インターフェイス、および構造体の本体。 これにより、本体全体のインデントを開始キーワードよりも下げずに、型定義の開始キーワードを型名と同じ行に含めることができるスタイルに対応できます。

    type IMyInterface = interface 
       abstract Function1: int -> int
    end
    

参照

その他の技術情報

F# 言語リファレンス