概要
F# では値と名前を関連付けることを 束縛 または バインド といいます。 この束縛を行ううえで let というキーワードを使うので let 束縛 とも呼ばれます。
束縛された変数は基本的に 再代入できません。
手続き型言語を使ったことがある方は 再代入 によって変数を意図的に変更したことがあると思います。 しかし、F# においては基本的には let 束縛をした変数にはそのような操作はできません。このような制約は手続き型言語に慣れ親しんだ方からすると厳しいものに感じることでしょう。 再代入なしでどうやって実用的なプログラムを書けばいいのか、と思う方もいるかもしれません。
ではなぜ F# では再代入ができない変数がメインで使われているのでしょうか? 実は、このような制約を設けることで可読性や保守性を高めたり、処理の並列化がしやすくなる場合があるのです。
変数が変更可能な場合、その変数を使っている箇所での実際の値は変数定義箇所を見ただけでは確定しません。 もしかするとどこかで書き変わっている可能性があるため、それまでにその変数に書き込んでいる部分をすべてみないと、その変数の値がどうなっているのかを確かめることはできません。
ほかにも、複数スレッドから変数にアクセスする可能性がある場合、変更可能な場合は適切にロックをかけないと別のスレッドに値を書き換えられてしまい、想定外の結果になってしまうこともあります。
これらの問題を避けるために、F# では変数は基本的に再代入が禁止されているのです。
サンプル
let 束縛は let 名前 = 式
の形を取ります。
いくつかの例を見てみましょう。
難しいことは何もなく、とても簡単に let 束縛を使えました。 それでは実際に let 束縛した変数を計算に使ってみましょう。
このように整数値を直接計算させた場合と遜色なく利用できます。
F# では再代入には <-
演算子を使いますが、通常の let 束縛では禁止されているため使えません。
let mutable
このように、F# では基本的に変数は再代入が禁止されていますが、mutable
キーワードを使うことで再代入できる変数も使えます。
先ほどコンパイルエラーになった例に mutable を付けてみましょう。
このように、変更可能な変数を定義するためには let mutable 名前 = 式
のように let の後ろに mutable を指定します。
mutable 付きの let 束縛は主にパフォーマンスを重視するときに使われます。 また、mutable な値は通常、関数内などの狭いスコープの中でのみの使用にとどめるようにしましょう。 スコープが広いと様々なところで読み書きが発生するようなコードが書けてしまうため、バグの混入の確率を高めてしまいます。
高度なトピック: let 式と let 定義
実は、F# の let 束縛には 2つの種類があります。 それぞれ書ける場所や構文が実は違うのですが、普段は気にしなくてもプログラムが書けるように工夫されているため、通常は意識する必要はありません。 F# の言語仕様に興味を持った場合は、このセクションも確認してみましょう。 まだ紹介していない関数やモジュールの定義を使っています。
let 式
言語仕様上は 値定義式(Value Definition Expressions) として書かれています。
「式」と名前が付くように、let 式は評価すると値になります(もしくは、例外が投げられます)。
let 式は単純化すると let 名前 = 式1 in 式2
という形をとります。
しかし、通常は in
を改行とインデントで置き換えているため、式2
の存在を意識することはあまりありません。
このコードでの y
を定義している部分が let 式です。
C# や Java、Python、TypeScript では変数定義は式ではなく文のため、関数の最後に変数定義があっても問題ありません。
しかし、F# では関数内での let 束縛は通常 let 式になるため、in
に続く 式2
がない場合はコンパイルエラーになります。
また、let 式は式のため、式が出現する箇所にはどこにでも書けます。
式が許される場所に let 式を埋め込んで遊んでみましょう。
let 定義
言語仕様上は モジュール内関数・値定義(Function and Value Definitions in Modules) などとして書かれています。
「など」としているのはクラスの class-function-or-value-defns
でも似たようなものが現れるのですが、そちらには言語仕様内で名前が付いていないためこのように表しています。
let 定義は単純化すると let 名前 = 式
という形をとります。
式ではないため、let 定義は二つ目の式を持ちません。
少々ややこしいことに、モジュールは do が書けるうえ、do キーワードが省略できるので、モジュールの最後に in
を伴った let 定義が書けるように見えてしまいます。
しかし、このコードは do を明示した次のコードと同じ意味になります。
ちなみに、モジュールの最後以外の場所で let に in を付けようとしてもエラーになります。 これは、モジュール直下には let 定義しかこないと想定しているためでしょう。 そうすると、モジュールの最後だけ let 式が許されている方がちょっとだけ微妙な感じがしてきます。