F#の構文は基本的にわかりやすいのですが、よくあるインデントエラーを避けるために知っておくべきルールがいくつかあります。Pythonのような、同じく空白文字に敏感な言語に慣れている方は注意してください。F#のインデントルールは微妙に異なります。
インデントと「オフサイド」ルール
サッカーでいうオフサイドルールは、特定の状況下で、選手がボールよりも前にいることができないというものです。選手が越えてはいけない線を「オフサイドライン」と呼びます。F#では、インデントが始まるべき位置を示す線に同じ用語を使っています。サッカーと同様に、ペナルティを避けるコツは、ラインの位置を知り、それを越えないことです。
一般に、オフサイドラインが設定されると、すべての式はそのラインにそのラインに揃っていなければなりません。
//文字列の列
//3456789
let f =
let x=1 // オフサイドラインは列3にあります
let y=1 // この行は列3から始める必要があります
x+y // この行も列3から始める必要があります
let f =
let x=1 // オフサイドラインは列3にあります
x+1 // おっと!列4から始めてはいけません
// error FS0010: 予期しない 識別子 です 束縛内。
let f =
let x=1 // オフサイドラインは列3にあります
x+1 // オフサイド!ボールより前に出ています!
// error FS0588: この 'let' に続くブロックが完了していません。
さまざまなトークンが新しいオフサイドラインを作るきっかけになります。たとえば、F#が let 式で使う =
を見つけると、次に出てくる記号や単語の位置に新しいオフサイドラインができます。
//文字列の列
//34567890123456789
let f = let x=1 // ラインは今、列11にあります("let x="の開始位置)
x+1 // これ以降は列11から始める必要があります
// | // 列11にオフサイドライン
let f = let x=1 // ラインは今、列11にあります("let x="の開始位置)
x+1 // オフサイド!
// | // 列4にオフサイドライン
let f =
let x=1 // = 記号の後の最初の単語がラインを決めます
// オフサイドラインは今、列4にあります
x+1 // これ以降は列4から始める必要があります
かっこ、 then
、 else
、 try
、 finally
、 do
、そしてマッチ句の ->
など、他のトークンも同じように働きます。
//文字列の列
//34567890123456789
let f =
let g = (
1+2) // "(" の後の最初の文字が
// 列5に新しいラインを決めます
g
let f =
if true then
1+2 // "then" の後の最初の文字が
// 列5に新しいラインを決めます
let f =
match 1 with
| 1 ->
1+2 // マッチの "->" の後の最初の文字が
// 列8に新しいラインを決めます
オフサイドラインは入れ子にでき、予想通りにプッシュとポップが行われます。
//文字列の列
//34567890123456789
let f =
let g = let x = 1 // "let g =" の後の最初の単語が
// 列12に新しいオフサイドラインを決めます
x + 1 // "x" は列12に揃える必要があります
// ここでオフサイドラインスタックをポップします
g + 1 // 前のラインに戻ります。"g" は列4に
// 揃える必要があります
新しいオフサイドラインは、スタック上の前のラインよりも前に進めません。
let f =
let g = ( // let が列4に新しいラインを決めます
1+2) // おっと!4未満の新しいラインは決められません
g
特殊なケース
コードのフォーマットをより柔軟にするため、いくつかの特殊なケースが作られています。 if-then-else
式や try-catch
式の各部分の開始位置を揃えるなど、多くは自然に感じられるでしょう。しかし、中にはわかりにくいものもあります。
+
、 |>
、 >>
などの中置演算子は、その長さプラス1スペース分だけラインの外側にあっても大丈夫です。
//文字列の列
//34567890123456789
let x = 1 // 列10に新しいラインを決めます
+ 2 // "+"はラインの外側にあっても問題ありません
+ 3
let f g h = g // 列15に新しいラインを決めます
>> h // ">>"はラインの外側にあっても問題ありません
中置演算子が行の先頭にある場合、その行は整列について厳格でなくても大丈夫です。
let x = 1 // 列10に新しいラインを決めます
+ 2 // 行頭の中置演算子は数えません
* 3 // "*"で始まるので、揃える必要はありません
- 4 // "-"で始まるので、揃える必要はありません
fun
キーワードが式の先頭にある場合、 fun
は新しいオフサイドラインを始めません。
//文字列の列
//34567890123456789
let f = fun x -> // "fun"は列9に新しいラインを決めるはずですが、
let y = 1 // そうではありません。実際のラインはここから始まります。
x + y
さらに詳しく知るには
インデントの仕組みについては、さらに多くの詳細がありますが、一般的なケースのほとんどは上記の例でカバーできるはずです。もっと詳しく知りたい場合は、F#の完全な言語仕様がダウンロード可能なPDFとして公開されています。一読をお勧めします。
「冗長」構文
F#はデフォルトでインデントを使ってブロック構造を示します。これを「軽量」構文と呼びます。インデントを使わない代わりの構文もあり、これを「冗長」構文と呼びます。冗長構文では、インデントを使う必要がなく、空白文字も重要ではありません。ただし、その代わりに以下のような多くのキーワードを使う必要があります。
- すべての
let
とdo
束縛の後にin
キーワード - if-then-else などのコードブロックに
begin
/end
キーワード - ループの終わりに
done
キーワード - 型定義の始まりと終わりにキーワード
以下は、通常では許されないような変わったインデントを使った冗長構文の例です。
#indent "off"
let f =
let x = 1 in
if x=2 then
begin "a" end else begin
"b"
end
#indent "on"
冗長構文は 「軽量」 モードでも常に使えます。時には、それが便利なこともあります。たとえば、 let
を1行の式に埋め込みたい場合などです。
let x = let y = 1 in let z = 2 in y + z
その他に、冗長構文を使いたくなるケースは次のとおりです。
- 生成されたコードを出力する場合
- OCamlと互換性を持たせる場合
- 視覚障害または盲目で、スクリーンリーダーを使う場合
- または、F#パーサーが使う抽象構文木について理解を深めたい場合
これらのケース以外では、冗長構文が実際に使われることはほとんどありません。