Rekaman (F#)
Catatan mewakili agregat sederhana dari nilai bernama, secara opsional dengan anggota. Rekaman ini dapat berupa struktur atau jenis referensi. Rekaman ini berjenis referensi secara default.
Sintaks
[ attributes ]
type [accessibility-modifier] typename =
{ [ mutable ] label1 : type1;
[ mutable ] label2 : type2;
... }
[ member-list ]
Keterangan
Dalam sintaks sebelumnya, typename merupakan nama jenis rekaman, label1 dan label2 merupakan nama nilai, yang disebut sebagai label, dan type1 dan type2 merupakan jenis nilai ini. member-list merupakan daftar anggota opsional untuk jenis tersebut. Anda dapat menggunakan atribut [<Struct>]
untuk membuat rekaman struktur daripada rekaman yang berjenis referensi.
Berikut ini adalah beberapa contohnya.
// 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 }
Ketika setiap label berada di garis terpisah, titik koma bersifat opsional.
Anda dapat mengatur nilai dalam ekspresi yang dikenal sebagai ekspresi rekaman. Kompilator menyimpulkan jenis dari label yang digunakan (jika label cukup berbeda dari label jenis rekaman lainnya). Kurung kurawal ({ }) mengapit ekspresi rekaman. Kode berikut menunjukkan ekspresi rekaman yang menginisialisasi rekaman dengan tiga elemen float dengan label x
, y
, dan z
.
let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }
Jangan gunakan bentuk yang dipersingkat jika ada jenis lain yang mungkin memiliki label yang sama.
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 }
Label dari jenis yang dinyatakan terkini lebih diutamakan daripada jenis yang dinyatakan terlebih dahulu, jadi dalam contoh sebelumnya, mypoint3D
disimpulkan menjadi Point3D
. Anda dapat menentukan secara eksplisit jenis rekaman, seperti pada kode berikut.
let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }
Metode dapat didefinisikan untuk jenis rekaman sama seperti untuk jenis kelas.
Membuat Rekaman dengan Menggunakan Ekspresi Rekaman
Anda dapat menginisialisasi rekaman dengan menggunakan label yang ditentukan dalam rekaman. Ekspresi yang melakukan ini disebut sebagai ekspresi rekaman. Gunakan kurung kurawal untuk mengapit ekspresi rekaman dan gunakan titik koma sebagai pemisah.
Contoh berikut ini menampilkan cara membuat rekaman.
type MyRecord = { X: int; Y: int; Z: int }
let myRecord1 = { X = 1; Y = 2; Z = 3 }
Titik koma setelah bidang terakhir dalam ekspresi rekaman dan dalam definisi jenis bersifat opsional, terlepas dari apakah semua bidang berada dalam satu baris atau tidak.
Saat membuat rekaman, Anda harus memberikan nilai untuk setiap bidang. Anda tidak dapat merujuk ke nilai bidang lain dalam ekspresi inisialisasi untuk bidang apa pun.
Dalam kode berikut, jenis myRecord2
disimpulkan dari nama bidang. Secara opsional, Anda dapat menentukan nama jenis secara eksplisit.
let myRecord2 =
{ MyRecord.X = 1
MyRecord.Y = 2
MyRecord.Z = 3 }
Bentuk konstruksi rekaman lain dapat digunakan ketika Anda harus menyalin rekaman yang sudah ada, dan kemungkinan mengubah beberapa nilai bidang. Kode berikut menggambarkan hal ini.
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
Bentuk ekspresi rekaman ini disebut ekspresi rekaman salin dan perbarui.
Rekaman tidak dapat diubah secara default; tetapi, Anda dapat dengan mudah membuat rekaman yang dimodifikasi dengan menggunakan ekspresi salin dan perbarui. Anda juga dapat secara eksplisit menentukan bidang yang dapat diubah.
type Car =
{ Make: string
Model: string
mutable Odometer: int }
let myCar =
{ Make = "Fabrikam"
Model = "Coupe"
Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
Jangan gunakan atribut DefaultValue pada bidang rekaman. Pendekatan yang lebih baik adalah menentukan instans rekaman default dengan bidang yang diinisialisasi ke nilai default, yang kemudian menggunakan ekspresi rekaman salin dan perbarui untuk mengatur bidang apa pun yang berbeda dari nilai default.
// 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 }
Membuat Rekaman yang Saling Rekursif
Terkadang saat membuat rekaman, Anda mungkin ingin membuatnya bergantung pada jenis lain yang ingin Anda tentukan setelahnya. Hal ini akan memunculkan kesalahan kompilasi, kecuali jika Anda menentukan jenis rekaman yang akan saling rekursif.
Menentukan rekaman yang saling rekursif dilakukan dengan kata kunci and
. Kata kunci ini memungkinkan Anda untuk menautkan 2 atau beberapa jenis rekaman bersama-sama.
Misalnya, kode berikut mendefinisikan jenis Person
dan Address
sebagai saling rekursif:
// 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 }
Untuk membuat instans keduanya, lakukan hal berikut:
// 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
}
}
Jika Anda menentukan contoh sebelumnya tanpa kata kunci and
, rekaman tidak akan dikompilasi. Kata kunci and
diperlukan untuk definisi yang saling rekursif.
Pencocokan Pola dengan Rekaman
Rekaman dapat digunakan untuk pencocokan pola. Anda dapat menentukan beberapa bidang secara eksplisit dan menyediakan variabel untuk bidang lain yang akan ditetapkan saat kecocokan terjadi. Contoh kode berikut menggambarkan hal ini.
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 }
Output dari kode ini adalah sebagai berikut.
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
Rekaman dan anggota
Anda dapat menentukan anggota pada rekaman seperti pada kelas. Tidak ada dukungan untuk bidang. Pendekatan umum adalah mendefinisikan anggota statis Default
untuk konstruksi rekaman yang mudah:
type Person =
{ Name: string
Age: int
Address: string }
static member Default =
{ Name = "Phillip"
Age = 12
Address = "123 happy fun street" }
let defaultPerson = Person.Default
Jika Anda menggunakan pengidentifikasi mandiri, pengidentifikasi tersebut akan merujuk ke instans rekaman yang anggotanya dipanggil:
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()
Perbedaan Antara Rekaman dan Kelas
Bidang rekaman berbeda dari bidang kelas karena bidang tersebut secara otomatis terekspos sebagai properti, dan bidang tersebut digunakan dalam pembuatan dan penyalinan rekaman. Konstruksi rekaman juga berbeda dari konstruksi kelas. Dalam jenis rekaman, Anda tidak dapat menentukan konstruktor. Sebaliknya, sintaks konstruksi yang dijelaskan dalam topik ini dapat diterapkan. Kelas tidak memiliki hubungan langsung antara parameter, bidang, dan properti konstruktor.
Seperti jenis gabungan dan struktur, rekaman memiliki semantik kesetaraan struktural. Kelas memiliki semantik kesetaraan referensi. Contoh kode berikut menunjukkan hal ini.
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."
Output dari kode ini adalah sebagai berikut:
The records are equal.
Jika Anda menulis kode yang sama dengan kelas, dua objek kelas akan tidak setara karena kedua nilai akan mewakili dua objek pada tumpukan dan hanya alamatnya yang akan dibandingkan (kecuali jika jenis kelas mengambil alih metode System.Object.Equals
).
Jika Anda memerlukan kesetaraan referensi untuk rekaman, tambahkan atribut [<ReferenceEquality>]
di atas rekaman.