単純な関数をもう一度見てみましょう。
let add1 x = x + 1
この "x" は何を意味するのでしょうか?2つの役割があります。
- 入力定義域から値を受け取る。
- その値に "x" という名前をつけて、後で参照できるようにする。
この、値に名前をつけるプロセスを「束縛」と呼びます。名前 "x" が入力値に「束縛」されるのです。
たとえば、この関数に5を入力する場合を考えてみましょう。元の定義の "x" をすべて "5" に置き換えることになります。ワープロの検索と置換のようなイメージです。
let add1 x = x + 1
add1 5
// "x" を "5" に置換
// add1 5 = 5 + 1 = 6
// 結果は 6
ここで重要なのは、これが代入ではないということです。"x" は値を割り当てる「スロット」や変数ではありません。後で別の値に変更することはできません。"x" という名前と値を一度だけ関連付けるのです。値は事前に定義された整数の1つで、変更できません。そのため、一度束縛されたxも変更できません。値と一度関連付けられたら、常にその値と関連付けられたままです。
この概念は関数型思考の重要な部分です。「変数」は存在せず、あるのは値だけです。
関数値
さらに考えてみましょう。 add1
という名前自体が「入力に1を足す関数」への束縛にすぎないことがわかります。関数自体は、束縛される名前とは独立しているのです。
let add1 x = x + 1
と入力すると、F#コンパイラに次のように伝えていることになります。「 add1
という名前を見つけたら、それを入力に1を足す関数に置き換えてください」。この add1
を関数値と呼びます。
関数が名前から独立していることを確認するには、次のコードを試してみてください。
let add1 x = x + 1
let plus1 = add1
add1 5
plus1 5
add1
と plus1
が同じ関数を参照する(束縛された)2つの名前であることがわかります。
関数値は、そのシグネチャで常に識別できます。シグネチャは 定義域 -> 値域
という標準形式を持ちます。一般的な関数値のシグネチャは次のようになります。
val 関数名 : 定義域 -> 値域
単純な値
次に、常に整数5を返し、入力を持たない操作を想像してみてください。
これは「定数」操作と呼べるでしょう。
F#でこれをどのように書くのでしょうか?F#コンパイラに「 c
という名前を見つけたら、それを 5 に置き換えてください」と伝えたいわけです。方法は次の通りです。
let c = 5
これを評価すると、次の結果が返ります。
val c : int = 5
今回は写像を示す矢印はなく、単一のintだけです。新しい点は、等号の後に実際の値が表示されていることです。F#コンパイラは、この束縛が常に返す既知の値(この場合は5)を持っていることを認識しています。
つまり、これは定数の定義、あるいはF#の用語では単純な値の定義です。
単純な値は関数値と区別できます。すべての単純な値は次のようなシグネチャを持ちます。
val 名前: 型 = 定数 // 矢印がないことに注意
単純な値 vs. 関数値
F#では、C#のような言語とは異なり、単純な値と関数値の間にほとんど違いがないことが重要です。どちらも名前に束縛できる値であり(同じキーワード let
を使います)、引数として渡すことができます。実際、関数型思考の重要な側面の1つは、まさにこの点です。 関数は他の関数への入力として渡すことができる値である。 これについてはすぐに見ていきます。
ただし、単純な値と関数値には微妙な違いがあります。関数には常に定義域と値域があり、結果を得るには引数に「適用」する必要があります。一方、単純な値は束縛された後に評価する必要はありません。たとえば、5を返す「定数関数」を定義したい場合は、次のようにする必要があります。
let c = fun()->5
// または
let c() = 5
これらの関数のシグネチャは以下のようになります。
val c : unit -> int
次のようにはなりません。
val c : int = 5
unit、関数構文、匿名関数については後ほど詳しく説明します。
「値」vs.「オブジェクト」
F#のような関数型プログラミング言語では、ほとんどのものを「値」と呼びます。一方、C#のようなオブジェクト指向言語では、ほとんどのものを「オブジェクト」と呼びます。では、「値」と「オブジェクト」の違いは何でしょうか?
値は、単に定義域のメンバーです。整数の定義域、文字列の定義域、整数を文字列に写像する関数の定義域などがあります。原則として、値は不変です。また、値には振る舞いが付随していません。
一方、オブジェクトは、標準的な定義によれば、データ構造とそれに関連する振る舞い(メソッド)のカプセル化です。一般的に、オブジェクトは状態を持つ(つまり可変である)ことが期待されます。また、内部状態を変更するすべての操作はオブジェクト自体が提供する必要があります(「ドット」表記を介して)。
F#では、プリミティブな値でさえもオブジェクトのような振る舞いを持っています。たとえば、文字列にドットを使って長さを取得できます。
"abc".Length
しかし、一般的にF#では標準的な値に対して「オブジェクト」という用語は使いません。「オブジェクト」は、真のクラスのインスタンスやメンバーメソッドを公開する他の値を指す場合に限って使います。
値の命名
値や関数の名前には標準的な命名規則が使われます。基本的には、アンダースコアを含むすべてのアルファベットと数字の文字列が使えます。いくつか特別なルールがあります。
- アポストロフィは、最初の文字以外ならどこにでも置けます。たとえば、
A'b'c begin' // 有効な名前
- 末尾のアポストロフィは、値の「派生」バージョンを示すのによく使われます。
let f = x
let f' = derivative f
let f'' = derivative f'
- 既存のキーワードの派生を定義するのにも使えます。
let if' b t f = if b then t else f
- 任意の文字列を二重バッククォートで囲むと、有効な識別子になります。
``this is a name`` ``123`` //有効な名前
二重バッククォートのテクニックは以下のような場合に便利です。
- キーワードと同じ識別子を使いたい場合。
let ``begin`` = "begin"
- ビジネスルール、単体テスト、またはCucumberのようなBDDスタイルの実行可能な仕様で自然言語を使いたい場合。
let ``初回顧客か?`` = true
let ``注文にギフトを追加`` = ()
if ``初回顧客か?`` then ``注文にギフトを追加``
// 単体テスト
let [<Test>] ``入力が2の場合、二乗は4になることを期待``=
// ここにコード
// BDD句
let [<Given>] ``カートに (.*) 個の商品がある`` (n:int) =
// ここにコード
F#の命名規則には、C#とは異なる点があります。F#では、他の.NET言語に公開するように設計されていない限り、関数と値は大文字ではなく小文字で始めます( PascalCase
ではなく camelCase
)。ただし、型とモジュールは大文字で始めます。