クラス (F#)
クラスは、プロパティ、メソッド、およびイベントを持つことができるオブジェクトを表す型です。
// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
解説
クラスは、F# でのオブジェクト指向プログラミングをサポートする主要な型の概念で、.NET オブジェクト型の基本的な記述を表します。
上記の構文で、type-name は任意の有効な識別子です。 type-params は省略可能なジェネリック型パラメーターを示します。 これは、山かっこ (< と >) で囲まれた型パラメーターの名前と制約で構成されます。 詳細については、「ジェネリック (F#)」および「制約 (F#)」を参照してください。 parameter-list はコンストラクター パラメーターを示します。 1 つ目のアクセス修飾子はこの型に属し、2 つ目のアクセス修飾子はプライマリ コンストラクターに属します。 どちらの場合も、既定値は public です。
クラスの基本クラスを指定するには、inherit キーワードを使用します。 基本クラス コンストラクターには、引数をかっこで囲んで指定する必要があります。
クラスに対してローカルになるフィールドまたは関数値を宣言するには、let 束縛を使用すると共に、let 束縛の一般的な規則に従う必要があります。 do-bindings セクションには、オブジェクトの構築時に実行されるコードが含まれます。
member-list は、追加のコンストラクター、インスタンスと静的メソッドの宣言、インターフェイス宣言、抽象束縛、およびプロパティとイベントの宣言で構成されます。 これらの項目については、「メンバー (F#)」を参照してください。
省略可能な as キーワードと共に使用される identifier により、インスタンス変数に名前 (自己識別子) が付けられます。この名前を使用して、型定義で型のインスタンスを参照できます。 詳細については、このトピックで後述する「自己識別子」を参照してください。
定義の開始と終了を示す class キーワードと end キーワードは省略可能です。
互いに参照し合う型である相互再帰型は、相互再帰関数と同様に、and キーワードを使用して結合されます。 例については、「相互再帰型」を参照してください。
コンストラクター
コンストラクターは、クラス型のインスタンスを作成するコードです。 F# のクラスのコンストラクターは、その他の .NET 言語の場合とは機能が多少、異なります。 F# クラスには、型名の後に続く parameter-list で引数が指定されるプライマリ コンストラクターが常に存在します。このコンストラクターの本体は、クラス宣言の先頭の let (および let rec) 束縛とその後に続く do 束縛で構成されます。 プライマリ コンストラクターの引数のスコープは、クラス宣言全体です。
コンストラクターを追加するには、次のように new キーワードを使用してメンバーを追加します。
new(argument-list) = constructor-body
新しいコンストラクターの本体は、クラス宣言の先頭で指定されたプライマリ コンストラクターを呼び出す必要があります。
この概念の例を次に示します。 次のコードでは、MyClass に 2 つのコンストラクターが指定されています。プライマリ コンストラクターは 2 つの引数を受け取り、もう 1 つのコンストラクターは引数を受け取りません。
type MyClass1(x: int, y: int) =
do printfn "%d %d" x y
new() = MyClass1(0, 0)
let 束縛と do 束縛
クラス定義の let 束縛と do 束縛によってプライマリ クラス コンストラクターの本体が形成されるので、これらの束縛はクラス インスタンスを作成するたびに実行されます。 let 束縛が関数の場合は、メンバーとしてコンパイルされます。 let 束縛が関数またはメンバーでは使用されない値の場合は、コンストラクターに対してローカルな変数としてコンパイルされます。 それ以外の場合は、クラスのフィールドとしてコンパイルされます。 その後に続く do 式は、プライマリ コンストラクターとしてコンパイルされ、すべてのインスタンスの初期化コードを実行します。 追加のコンストラクターではプライマリ コンストラクターが常に呼び出されるため、let 束縛と do 束縛は、呼び出されるコンストラクターに関係なく、常に実行されます。
let 束縛によって作成されるフィールドには、クラスのメソッドおよびプロパティ全体からアクセスできます。ただし、静的メソッドがパラメーターとしてインスタンス変数を受け取る場合でも、静的メソッドからはこのフィールドにアクセスできません。 また、自己識別子 (存在する場合) を使用してアクセスすることもできません。
自己識別子
自己識別子は現在のインスタンスを表す名前です。 自己識別子は、C# または C++ の this キーワードや Visual Basic の Me に似ています。 自己識別子のスコープをクラス定義全体にするか、個々のメソッドだけにするかに応じて、2 種類の方法で自己識別子を定義できます。
自己識別子をクラス全体に対して定義するには、コンストラクターのパラメーター リストの右かっこの後で as キーワードを使用して、識別名を指定します。
自己識別子を 1 つのメソッドだけに対して定義するには、メンバー宣言のメソッド名と区切り記号のピリオド (.) の直前で自己識別子を指定します。
次のコード例は、自己識別子を作成する 2 つの方法を示しています。 最初の行では、as キーワードを使用して自己識別子を定義しています。 5 番目の行では、this 識別子を使用して、スコープが PrintMessage メソッドに制限される自己識別子を定義しています。
type MyClass2(dataIn) as self =
let data = dataIn
do
self.PrintMessage()
member this.PrintMessage() =
printf "Creating MyClass2 with Data %d" data
その他の .NET 言語とは異なり、自己識別子には任意の名前を指定できます。self、Me、this などの名前に限定されません。
as キーワードで宣言された自己識別子は、let 束縛が実行されるまで初期化されません。 したがって、let 束縛では使用できません。 do 束縛セクションでは自己識別子を使用できます。
ジェネリック型パラメーター
ジェネリック型パラメーターは、単一引用符の後に識別子が続く形式で、山かっこ (< と >) で囲んで指定します。 複数のジェネリック型パラメーターがある場合は、コンマ (,) で区切ります。 ジェネリック型パラメーターの対象範囲は、宣言全体です。 ジェネリック型パラメーターを指定する方法を次のコード例に示します。
type MyGenericClass<'a> (x: 'a) =
do printfn "%A" x
型引数は、型の使用時に推論されます。 次のコードでは、タプルのシーケンスが推論された型です。
let g1 = MyGenericClass( seq { for i in 1 .. 10 -> (i, i*i) } )
継承の指定
inherit 句では、直接基本クラス (存在する場合) を指定します。 F# では、指定できる直接基本クラスは 1 つだけです。 クラスによって実装されるインターフェイスは、基本クラスとは見なされません。 インターフェイスについては、「インターフェイス (F#)」を参照してください。
基本クラスのメソッドおよびプロパティに派生クラスからアクセスするには、識別子として base 言語キーワードを使用し、その後にピリオド (.) とメンバーの名前を指定します。
詳細については、「継承 (F#)」を参照してください。
メンバー セクション
このセクションでは、静的メソッド、インスタンス メソッド、プロパティ、インターフェイス実装、抽象メンバー、イベント宣言、および追加のコンストラクターを定義できます。 let 束縛と do 束縛は、このセクションでは使用できません。 クラスだけでなく、さまざまな F# の型にメンバーを追加できるため、メンバーについては、「メンバー (F#)」で別途説明します。
相互再帰型
互いに循環して参照し合う型を定義する場合は、and キーワードを使用して型定義を結合します。 and キーワードによって、最初の定義を除くすべての定義の type キーワードが次のように置き換えられます。
open System.IO
type Folder(pathIn: string) =
let path = pathIn
let filenameArray : string array = Directory.GetFiles(path)
member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray
and File(filename: string, containingFolder: Folder) =
member this.Name = filename
member this.ContainingFolder = containingFolder
let folder1 = new Folder(".")
for file in folder1.FileArray do
printfn "%s" file.Name
出力されるのは、現在のディレクトリに格納されているすべてのファイルのリストです。
クラス、共用体、レコード、および構造体を使用する状況
さまざまな型を選択できるので、各型の使用目的を十分に理解して、特定の状況に合った型を選択する必要があります。 クラスは、オブジェクト指向プログラミングのコンテキストで使用するように設計されています。 オブジェクト指向プログラミングは、.NET Framework 用に記述されたアプリケーションで使用される最も優先度の高いパラダイムです。 F# コードが .NET Framework またはその他のオブジェクト指向ライブラリと緊密に連携する必要がある場合、また特に、UI ライブラリなどのオブジェクト指向型システムを拡張する必要がある場合は、クラスが適していると考えられます。
オブジェクト指向コードと緊密に相互運用しない場合、または単体で使用できるのでオブジェクト指向コードと頻繁にやり取りする必要がないコードを記述している場合は、レコードおよび判別共用体の使用を検討してください。 多くの場合、よく検討された単一の判別共用体と適切なパターン マッチ コードを、オブジェクト階層に代わる簡単な手法として使用できます。 判別共用体の詳細については、「判別共用体 (F#)」を参照してください。
レコードには、クラスよりも簡単に使用できるという利点がありますが、型で要求されるレベルが、簡略化されたレコードで実現できるレベルを超えている場合には適していません。 レコードは、基本的には値の単純な集約であり、カスタム動作を実行できる個別のコンストラクター、隠しフィールド、継承、およびインターフェイス実装はいずれも含まれません。 プロパティやメソッドなどのメンバーをレコードに追加して動作をより複雑にすることはできますが、レコードに格納されるフィールドは、値の単純な集約のままです。 レコードの詳細については、「レコード (F#)」を参照してください。
データの集約が小規模である場合は構造体も便利ですが、構造体は .NET 値型であるという点で、クラスおよびレコードとは異なります。 クラスやレコードは .NET 参照型です。 値型と参照型のセマンティクスは、値型が値渡しされる点で異なります。 つまり、パラメーターとして渡される場合、または関数から返される場合に、値型はビット単位でコピーされます。 また、値型はスタックに格納されるか、またはフィールドとして使用される場合は、ヒープ上のそれぞれの場所に格納されるのではなく、親オブジェクト内に埋め込まれます。 したがって、構造体は、ヒープへのアクセスのオーバーヘッドが問題になるような、アクセス頻度の高いデータに適しています。 構造体の詳細については、「構造体 (F#)」を参照してください。