Satuan ukur (units of measure) adalah anotasi pada tipe numerik yang membuat satuan fisik seperti meter dan detik ikut diperiksa saat kompilasi. Nilai float<m> dan float<s> menjadi dua tipe yang berbeda, sehingga kesalahan mencampur keduanya langsung ditolak compiler. Topik ini membahas pendefinisian satuan, operasi aritmetika yang membawa satuan, pesan error ketika satuan bercampur, serta konversi antar satuan.
Satuan sebagai bagian dari tipe#
Satuan dideklarasikan dengan atribut [<Measure>] pada satu baris pendek, dan literal float menerima akhiran satuan langsung pada angkanya. Deklarasi [<Measure>] type m memperkenalkan simbol m sebagai satuan, dan simbol itu kemudian digunakan sebagai akhiran pada literal maupun anotasi tipe. Compiler kemudian melacak satuan melalui seluruh operasi: penjumlahan menuntut dua sisi bersatuan sama, perkalian menggabungkan satuan, dan pembagian membentuk satuan turunan.
Berkas berikut mendefinisikan meter dan detik, menghitung kecepatan dari pembagian jarak dengan waktu, menggandakan jarak dengan perkalian, lalu menghapus satuan lewat pembagian besaran dengan dirinya.
[<Measure>] type m
[<Measure>] type s
let jarak = 100.0<m>
let waktu = 20.0<s>
// pembagian menghasilkan satuan turunan m/s
let kecepatan = jarak / waktu
printfn "kecepatan: %A" kecepatan
// perkalian dengan bilangan tanpa satuan tidak mengubah satuan
let jarakGanda = jarak * 2.0
printfn "jarak ganda: %A" jarakGanda
// satuan saling menghapus pada pembagian besaran dengan dirinya
printfn "rasio tanpa satuan: %A" (jarak / jarak)kecepatan: 5.0
jarak ganda: 200.0
rasio tanpa satuan: 1.0Nilai kecepatan bertipe float<m/s>, satuan turunan yang terbentuk sendiri dari pembagian. Anotasi tipe juga dapat dituliskan, misalnya let jarak: float<m> = 100.0<m>, dan compiler menuntut kedua sisi bersatuan sama. Perkalian jarak * 2.0 tetap menghasilkan float<m> karena 2.0 tidak bersatuan, dan pola ini digunakan untuk mengalikan besaran dengan koefisien tanpa satuan. Pembagian besaran dengan dirinya menghapus satuan dan menghasilkan bilangan polos, sebagaimana meter dibagi meter seharusnya. Sejak titik ini, kesalahan yang menukar jarak dengan waktu menjadi kesalahan tipe, bukan lagi kekeliruan yang baru terlihat saat program berjalan.
Compiler menolak satuan yang bercampur#
Penjumlahan meter dengan detik tidak memiliki makna fisik, dan compiler menolaknya dengan error FS0001. Berkas berikut sengaja menuliskan penjumlahan semacam itu.
// tidak-dijalankan: berkas ini sengaja mencampur dua satuan yang berbeda
[<Measure>] type m
[<Measure>] type s
let jarak = 100.0<m>
let waktu = 20.0<s>
// penjumlahan meter dengan detik tidak bermakna, sehingga ditolak compiler
let salah = jarak + waktu
printfn "%A" salahPerintah berikut menjalankan berkas tersebut. Pesan error yang muncul memang diharapkan, dan bagian jalur folder pada pesan disingkirkan agar ringkas.
# penjumlahan meter dengan detik ditolak compiler
dotnet fsi campur-satuan.fsx 2>&1 | sed "s|.*/||"campur-satuan.fsx(9,21): error FS0001: The unit of measure 's' does not match the unit of measure 'm'Pesan FS0001 menyebut kedua satuan yang tidak cocok beserta posisi persamaannya, yaitu kolom tempat waktu ditulis. Perbaikannya bukan menghapus pengecekan, melainkan mengoreksi rumus: apabila yang dimaksud adalah kecepatan, kedua nilai dibagi, dan apabila keduanya memang perlu dijumlahkan, salah satu nilainya menggunakan satuan yang keliru. Error semacam ini muncul pada waktu kompilasi, sebelum program pernah dijalankan.
Pemeriksaan ini menggantikan dua kebiasaan yang rawan. Yang pertama adalah menyimpan satuan di dalam nama variabel, misalnya jarakMeter, lalu mengandalkan kehati-hatian pada setiap persamaan. Yang kedua adalah memeriksa satuan saat program berjalan, yang menuntut kode tambahan dan hanya menemukan kesalahan pada jalur yang benar-benar dieksekusi. Satuan ukur memindahkan pemeriksaan itu ke compiler, sehingga seluruh jalur tercakup tanpa kode tambahan.
Konversi antar satuan#
Konversi dilakukan dengan aritmetika biasa karena satuan ikut terhitung. Membagi meter dengan faktor 1000.0<m/km> menghasilkan nilai dalam kilometer, sedangkan pembagian dua besaran yang satuannya sama menghapus satuan dari hasilnya. Fungsi float juga tersedia untuk melepas satuan secara eksplisit ketika hasilnya akan dikirim ke kode yang tidak mengenal satuan.
[<Measure>] type m
[<Measure>] type km
let jarakMeter = 42500.0<m>
// membagi dengan 1000.0<m/km> menghasilkan float<km>
let jarakKm = jarakMeter / 1000.0<m/km>
printfn "jarak: %A km" jarakKm
// fungsi float menghapus satuan dan mengembalikan float biasa
printfn "tanpa satuan: %A" (float jarakKm)jarak: 42.5 km
tanpa satuan: 42.5Arah sebaliknya juga lazim, yaitu melekatkan satuan pada bilangan yang berasal dari luar program. Nilai hasil input atau pembacaan berkas berupa float biasa, sehingga satuan harus dilekatkan kembali sebelum nilai itu boleh digunakan dalam perhitungan bersatuan.
[<Measure>] type m
// bacaan berasal dari luar program sehingga belum bersatuan
let bacaan = 250.0
let jarak = LanguagePrimitives.FloatWithMeasure<m> bacaan
printfn "%A" jarak250.0Fungsi FloatWithMeasure khusus untuk float, dan tipe bilangan lain memiliki padanannya sendiri, misalnya Int32WithMeasure untuk int. Fungsi-fungsi tersebut dibutuhkan ketika nilainya dihitung saat program berjalan, karena satuan pada tipe hanya dapat dituliskan pada literal secara langsung. Fungsi juga dapat ditulis secara generik terhadap satuan, yaitu dengan parameter bertipe float<'u>, sehingga satu fungsi melayani meter maupun detik tanpa membuang pemeriksaan satuan.
Satuan hanya hidup saat kompilasi#
Satuan adalah bagian dari tipe pada waktu kompilasi dan dihapus ketika program berjalan, sehingga tidak ada biaya kinerja atas penggunaannya. Nilai float<m> di memori berupa float biasa, dan cetakannya hanya menampilkan angka, sebagaimana terlihat pada keluaran-keluaran sebelumnya. Pemeriksaan satuan juga hanya berlaku pada tipe numerik, yaitu float, int, decimal, dan tipe bilangan lain yang mendukungnya.
Karena dihapus saat berjalan, satuan tidak dapat ditentukan secara dinamis dari string pada runtime. Jalur yang benar adalah membaca angka secara biasa, lalu melekatkan satuan yang sudah diketahui melalui FloatWithMeasure seperti pada contoh sebelumnya. Dengan pembatasan itu, satuan tetap menjadi pemeriksaan statis yang murah sekaligus menangkap kesalahan rumus lebih awal.
Perbandingan dengan pendekatan tanpa satuan memperlihatkan nilai tambahnya. Kode tanpa satuan mengandalkan konvensi penamaan dan komentar yang tidak diperiksa siapa pun, sedangkan satuan ukur menjadikan rumus fisik itu sendiri sebagai bagian dari tipe. Perubahan rumus yang keliru terlihat langsung pada waktu kompilasi, dan pemfaktoran ulang perhitungan menjadi lebih aman karena compiler menjaga konsistensi seluruh persamaan.
Ringkasan#
- Atribut
[<Measure>]mendefinisikan satuan, dan literal float menerima akhiran satuan seperti100.0<m>secara langsung. - Operasi aritmetika memperbarui satuan secara otomatis, sehingga pembagian jarak dengan waktu menghasilkan
float<m/s>. - Pencampuran satuan ditolak compiler dengan error FS0001, sebelum program pernah dijalankan.
- Konversi dilakukan dengan membagi faktor bersatuan, fungsi
floatmelepas satuan, danFloatWithMeasuremelekatkannya kembali.
Referensi#
- Units of Measure — Microsoft Learn — Dokumentasi resmi satuan ukur F#, termasuk satuan turunan dan konversi.
- Language reference F# — Microsoft Learn — Rujukan menyeluruh untuk sintaks bahasa F#.