レコード (F#)
レコードは、名前付きの値の単純な集合を表しており、オプションでメンバーを含みます。
[ attributes ]
type [accessibility-modifier] typename = {
[ mutable ] label1 : type1;
[ mutable ] label2 : type2;
...
}
member-list
解説
上の構文で、typename はレコード型の名前です。label1 および label2 は、値の名前であり、ラベルと呼ばれます。type1 および type2 は、これらの値の型です。 member-list は、型のメンバーのリストで、省略することもできます。
次にいくつかの例を示します。
type Point = { x : float; y: float; z: float; }
type Customer = { First : string; Last: string; SSN: uint32; AccountNumber : uint32; }
各ラベルが独立した行にある場合、セミコロンは省略可能です。
値は、レコード式と呼ばれる式で設定できます。 コンパイラは、使用されているラベルから型を推測します (ラベルが他のレコード型のラベルと十分に区別される場合)。 レコード式は中かっこ ({ }) で囲みます。 次のコードは、x、y、および z というラベルが付いた 3 つの float 要素を持つレコードを初期化するレコード式を示しています。
let mypoint = { x = 1.0; y = 1.0; z = -1.0; }
同じラベルを持つ別の型が存在する可能性がある場合は、短縮形を使用しないでください。
type Point = { x : float; y: float; z: float; }
type Point3D = { x: float; y: float; z: float }
// Ambiguity: Point or Point3D?
let mypoint3D = { x = 1.0; y = 1.0; z = 0.0; }
最も新しく宣言された型のラベルは、以前に宣言された型のラベルよりも優先されます。そのため、上の例では、mypoint3D は Point3D と推論されます。 次のコードに示すように、レコード型を明示的に指定できます。
let myPoint1 = { Point.x = 1.0; y = 1.0; z = 0.0; }
クラス型の場合と同様に、レコード型に対してメソッドを定義できます。
レコード式を使用したレコードの作成
レコード内で定義されているラベルを使用して、レコードを初期化できます。 この操作を行う式を、レコード式といいます。 中かっこを使用してレコード式を囲み、セミコロンを区切り記号として使用します。
レコードを作成する方法の例を次に示します。
type MyRecord = {
X: int;
Y: int;
Z: int
}
let myRecord1 = { X = 1; Y = 2; Z = 3; }
レコード式および型定義の最後のフィールドの後のセミコロンは、すべてのフィールドが 1 行に含まれるかどうかにかかわらず、省略可能です。
レコードを作成する場合は、各フィールドの値を指定する必要があります。 フィールドで、初期化式中の他のフィールドの値を参照することはできません。
次のコードでは、myRecord2 の型がフィールドの名前から推論されます。 オプションで、型の名前を明示的に指定できます。
let myRecord2 = { MyRecord.X = 1; MyRecord.Y = 2; MyRecord.Z = 3 }
レコード構築のもう 1 つの形式は、既存のレポートをコピーし、必要に応じてフィールド値の一部を変更する必要がある場合に便利です。 次のコード行は、これを示したものです。
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
この形式のレコード式を、レコード式のコピーおよび更新といいます。
レコードは既定で変更不可ですが、コピーおよび更新の式を使用して、変更されたレコードを簡単に作成できます。 また、変更可能なフィールドを明示的に指定できます。
type Car = {
Make : string
Model : string
mutable Odometer : int
}
let myCar = { Make = "Fabrikam"; Model = "Coupe"; Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
DefaultValue 属性は、レコードのフィールドを使用しないでください。 既定値に初期化されるフィールドを持つレコードの既定のインスタンスを定義およびコピーを使用して、既定値とは異なる任意のフィールドを設定するのには、レコード式を更新するより良いアプローチです。
// Rather than use [<DefaultValue>], define a default record.
type MyRecord =
{
field1 : int
field2 : int
}
let defaultRecord1 = { field1 = 0; field2 = 0 }
let defaultRecord2 = { field1 = 1; field2 = 25 }
// Use the with keyword to populate only a few chosen fields
// and leave the rest with default values.
let rr3 = { defaultRecord1 with field2 = 42 }
レコードを使用したパターン マッチ
パターン マッチでレコードを使用できます。 いくつかのレコードを明示的に指定し、一致が生じた場合に割り当てられる他のフィールドに対する変数を指定できます。 これを次のコード例に示します。
type Point3D = { x: float; y: float; z: float }
let evaluatePoint (point: Point3D) =
match point with
| { x = 0.0; y = 0.0; z = 0.0 } -> printfn "Point is at the origin."
| { x = xVal; y = 0.0; z = 0.0 } -> printfn "Point is on the x-axis. Value is %f." xVal
| { x = 0.0; y = yVal; z = 0.0 } -> printfn "Point is on the y-axis. Value is %f." yVal
| { x = 0.0; y = 0.0; z = zVal } -> printfn "Point is on the z-axis. Value is %f." zVal
| { x = xVal; y = yVal; z = zVal } -> printfn "Point is at (%f, %f, %f)." xVal yVal zVal
evaluatePoint { x = 0.0; y = 0.0; z = 0.0 }
evaluatePoint { x = 100.0; y = 0.0; z = 0.0 }
evaluatePoint { x = 10.0; y = 0.0; z = -1.0 }
このコードによって、次のような出力が生成されます。
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
レコードとクラスの違い
レコードのフィールドは、自動的にプロパティとして公開され、レコードの作成およびコピーで使用される点が、クラスとは異なります。 また、レコードの作成もクラスの作成とは異なります。 レコード型では、コンストラクターを定義できません。 代わりに、このトピックで説明した構築構文が適用されます。 クラスでは、コンストラクターのパラメーター、フィールド、およびプロパティの間に直接的な関係がありません。
共用体型および構造体型のように、レコードには構造的な等値セマンティクスがあります。 クラスには、参照が等値であるセマンティクスがあります。 ドメイン スコープを追加するコード例を次に示します。
type RecordTest = { X: int; Y: int }
let record1 = { X = 1; Y = 2 }
let record2 = { X = 1; Y = 2 }
if (record1 = record2) then
printfn "The records are equal."
else
printfn "The records are unequal."
クラスを使用して同じコードを作成した場合は、2 つの値がヒープ上の 2 つのオブジェクトを表し、アドレスのみが比較されるため (クラス型が System.Object.Equals メソッドをオーバーライドする場合を除く)、2 つのクラス オブジェクトは等しくなりません。