レコード (F#)
レコードは、名前付きの値の単純な集合を表しており、オプションでメンバーを含みます。 これは構造体または参照型のいずれかです。 既定では、参照型です。
構文
[ attributes ]
type [accessibility-modifier] typename =
{ [ mutable ] label1 : type1;
[ mutable ] label2 : type2;
... }
[ member-list ]
解説
前の構文で、typename はレコードの型の名前であり、label1 と label2 は値の名前であり、"ラベル" と呼ばれます。また、type1 と type2 はこのような値の型です。 member-list は、その型のメンバーの省略可能な一覧です。 [<Struct>]
属性を使用して、参照型であるレコードではなく、構造体レコードを作成できます。
次は一部の例です。
// Labels are separated by semicolons when defined on the same line.
type Point = { X: float; Y: float; Z: float }
// You can define labels on their own line with or without a semicolon.
type Customer =
{ First: string
Last: string
SSN: uint32
AccountNumber: uint32 }
// A struct record.
[<Struct>]
type StructPoint = { X: float; Y: float; Z: float }
各ラベルが個別の行にある場合、セミコロンは省略可能です。
"レコード式" と呼ばれる式に値を設定できます。 コンパイラにより、使用されているラベルから型が推測されます (ラベルが他のレコードの型と十分に異なる場合)。 レコード式は、中かっこ ({ }) を使用して囲みます。 次のコードは、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 }
既存のレコードをコピーする必要があり、場合によってはフィールド値の一部を変更する必要があるときは、別形式のレコードの構築が役立ちます。 次のコード行はその例です。
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 }
相互に再帰的なレコードの作成
レコードを作成するときに、後で定義する別の型に依存させたい場合があります。 相互に再帰的になるようにレコードの型を定義しない限り、これはコンパイル エラーになります。
相互に再帰なレコードを定義するには、and
キーワードを使用します。 これにより、2 つ以上のレコードの型をリンクすることができます。
たとえば、次のコードでは、Person
型と Address
型を相互に再帰的として定義しています。
// Create a Person type and use the Address type that is not defined
type Person =
{ Name: string
Age: int
Address: Address }
// Define the Address type which is used in the Person record
and Address =
{ Line1: string
Line2: string
PostCode: string
Occupant: Person }
両方のインスタンスを作成するには、次の手順を実行します。
// Create a Person type and use the Address type that is not defined
let rec person =
{
Name = "Person name"
Age = 12
Address =
{
Line1 = "line 1"
Line2 = "line 2"
PostCode = "abc123"
Occupant = person
}
}
and
キーワードを使用せずに前の例を定義すると、コンパイルできません。 相互に再帰的な定義には、and
キーワードが必要です。
レコードを使用したパターン マッチング
レコードはパターン マッチングに使用できます。 一部のフィールドは明示的に指定し、他のフィールドには一致が発生したときに割り当てられる変数を指定することができます。 これを次のコード例に示します。
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).
レコードとメンバー
クラスの場合と同じように、レコードのメンバーを指定できます。 フィールドはサポートされていません。 一般的なアプローチは、レコードを簡単に構築できるように Default
の静的メンバーを定義することです。
type Person =
{ Name: string
Age: int
Address: string }
static member Default =
{ Name = "Phillip"
Age = 12
Address = "123 happy fun street" }
let defaultPerson = Person.Default
自己識別子を使用する場合、その識別子は、メンバーが呼び出されるレコードのインスタンスを参照します。
type Person =
{ Name: string
Age: int
Address: string }
member this.WeirdToString() =
this.Name + this.Address + string this.Age
let p = { Name = "a"; Age = 12; Address = "abc123" }
let weirdString = p.WeirdToString()
レコードとクラスの違い
レコード フィールドは、プロパティとして自動的に公開され、レコードの作成とコピーに使用されるという点でクラス フィールドとは異なります。 レコードの構築も、クラスの構築とは異なります。 レコード型では、コンストラクターを定義することはできません。 代わりに、このトピックで説明する構築構文が適用されます。 クラスには、コンストラクターのパラメーター、フィールド、およびプロパティの間に直接的な関係はありません。
共用体や構造体の型と同様に、レコードには構造の等価性のセマンティクスがあります。 クラスには参照の等価性のセマンティクスがあります。 次のコード例はこの処理方法を示しています。
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."
このコードの出力は次のようになります。
The records are equal.
クラスを使用して同じコードを書いた場合、2 つのクラス オブジェクトは等しくなりません。これは、(クラスの型によって System.Object.Equals
メソッドがオーバーライドされない限り) 2 つの値はヒープ上の 2 つのオブジェクトを表し、アドレスのみが比較されるためです。
レコードの参照の等価性が必要な場合は、レコードの上に属性 [<ReferenceEquality>]
を追加します。
関連項目
.NET