F# documents in Japanese

Hi, F#er's!!

Follow me on GitHub

let束縛とは

概要

F# では値と名前を関連付けることを 束縛 または バインド といいます。 この束縛を行ううえで let というキーワードを使うので let 束縛 とも呼ばれます。

// xという変数は42という値に束縛される
let x = 42

// 42に束縛された変数xを使い、値を出力
printfn "%d" x

束縛された変数は基本的に 再代入できません

手続き型言語を使ったことがある方は 再代入 によって変数を意図的に変更したことがあると思います。 しかし、F# においては基本的には let 束縛をした変数にはそのような操作はできません。このような制約は手続き型言語に慣れ親しんだ方からすると厳しいものに感じることでしょう。 再代入なしでどうやって実用的なプログラムを書けばいいのか、と思う方もいるかもしれません。

ではなぜ F# では再代入ができない変数がメインで使われているのでしょうか? 実は、このような制約を設けることで可読性や保守性を高めたり、処理の並列化がしやすくなる場合があるのです。

変数が変更可能な場合、その変数を使っている箇所での実際の値は変数定義箇所を見ただけでは確定しません。 もしかするとどこかで書き変わっている可能性があるため、それまでにその変数に書き込んでいる部分をすべてみないと、その変数の値がどうなっているのかを確かめることはできません。

ほかにも、複数スレッドから変数にアクセスする可能性がある場合、変更可能な場合は適切にロックをかけないと別のスレッドに値を書き換えられてしまい、想定外の結果になってしまうこともあります。

これらの問題を避けるために、F# では変数は基本的に再代入が禁止されているのです。

サンプル

let 束縛は let 名前 = 式 の形を取ります。 いくつかの例を見てみましょう。

// 数値リテラル
let number = 100

// 文字列リテラル
let title = "Fun Fan F#!!"

// 関数値
let func = fun x -> x * 2

難しいことは何もなく、とても簡単に let 束縛を使えました。 それでは実際に let 束縛した変数を計算に使ってみましょう。

// oneHundredを 100 に束縛
let oneHundred = 100
// tripleを「整数値を 3 倍する関数値」に束縛
let triple = fun x -> 3 * x

// 実際に計算をさせてみる
let answer = triple oneHundred
// 300 が出力される
printfn $"%d{answer}"

このように整数値を直接計算させた場合と遜色なく利用できます。 F# では再代入には <- 演算子を使いますが、通常の let 束縛では禁止されているため使えません。

// oneHundredを 100 に束縛
let oneHundred = 100
// F# では再代入は禁止されている(コンパイルエラー)
oneHundred <- 200

let mutable

このように、F# では基本的に変数は再代入が禁止されていますが、mutable キーワードを使うことで再代入できる変数も使えます。 先ほどコンパイルエラーになった例に mutable を付けてみましょう。

// mutableな変数としてみる
let mutable number = 100

// let mutable としたので number を 200 に変更可能
number <- 200

このように、変更可能な変数を定義するためには 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 の存在を意識することはあまりありません。

// inを省略せずに書くとこうなる
let f x =
  let y = x + 1 in y * 2

// inを改行とインデントで置き換えるとこうなる
let g x =
  let y = x + 1
  y * 2

このコードでの y を定義している部分が let 式です。

C# や Java、Python、TypeScript では変数定義は式ではなく文のため、関数の最後に変数定義があっても問題ありません。 しかし、F# では関数内での let 束縛は通常 let 式になるため、in に続く 式2 がない場合はコンパイルエラーになります。

// 式2がないためコンパイルエラー
let f x =
  let y = x + 1

また、let 式は式のため、式が出現する箇所にはどこにでも書けます。

// 2 * (10 + 1)と同じ意味
2 * let x = 10 in x + 1

// 実は、
// let a = x + 1 in (let b = x * 2 in a + b)
// のinを省略して改行しているだけなので、
// bを定義するlet式は外側のaを定義するlet式の「式2」
let f x =
  let a = x + 1
  let b = x * 2
  a + b

式が許される場所に let 式を埋め込んで遊んでみましょう。

let 定義

言語仕様上は モジュール内関数・値定義(Function and Value Definitions in Modules) などとして書かれています。 「など」としているのはクラスの class-function-or-value-defns でも似たようなものが現れるのですが、そちらには言語仕様内で名前が付いていないためこのように表しています。 let 定義は単純化すると let 名前 = 式 という形をとります。 式ではないため、let 定義は二つ目の式を持ちません。

module M =
  // let 式ではないため、二つ目の式を持たない
  let x = 10

少々ややこしいことに、モジュールは do が書けるうえ、do キーワードが省略できるので、モジュールの最後に in を伴った let 定義が書けるように見えてしまいます。

module M =
  // inを伴った let 定義・・・?
  let x = 10 in printfn "%d" x

しかし、このコードは do を明示した次のコードと同じ意味になります。

module M =
  // doの一部
  do
    // let 定義ではなく、do の式部分に let 式を置いたもの
    let x = 10 in printfn "%d" x

ちなみに、モジュールの最後以外の場所で let に in を付けようとしてもエラーになります。 これは、モジュール直下には let 定義しかこないと想定しているためでしょう。 そうすると、モジュールの最後だけ let 式が許されている方がちょっとだけ微妙な感じがしてきます。

参考