Skrip dengan fsx

Menjalankan berkas skrip .fsx dengan dotnet fsi sebagai taman bermain interaktif untuk bereksperimen.

Daftar isi

Berkas .fsx adalah berkas skrip F# yang dieksekusi langsung oleh F# Interactive (fsi), alat bawaan .NET SDK, tanpa proyek dan tanpa langkah kompilasi terpisah. Topik ini membahas cara menjalankan skrip dalam mode satu kali (one-shot), perbedaannya dengan sesi interaktif, serta direktif #load dan #r untuk memuat berkas lain dan paket NuGet. Tiga contoh skrip di bawah mencakup konversi suhu, statistik nilai ujian, dan jadwal piket, semuanya dijalankan dengan dotnet fsi.

Menjalankan skrip satu kali#

Perintah dotnet fsi dengan nama berkas mengeksekusi skrip dari baris pertama sampai terakhir, lalu fsi keluar. Mode ini disebut satu kali (one-shot) karena seluruh berkas dievaluasi dalam satu kali eksekusi dan tidak ada state yang tersisa setelah proses berakhir. Berkas .fsproj tidak diperlukan, dan skrip cukup diletakkan di folder mana pun.

Berkas berikut mengonversi suhu dari Celsius ke Fahrenheit dan Kelvin. Keduanya dihitung dengan fungsi murni, sehingga keluarannya selalu sama untuk masukan yang sama. Berkasnya disimpan sebagai konversi-suhu.fsx.

fsharp konversi-suhu.fsx
// Konversi suhu dari Celsius ke Fahrenheit dan Kelvin.
let celsiusKeFahrenheit c = c * 9.0 / 5.0 + 32.0
let celsiusKeKelvin c = c + 273.15

let celsius = 25.0

printfn "Celsius    : %.1f" celsius
printfn "Fahrenheit : %.1f" (celsiusKeFahrenheit celsius)
printfn "Kelvin     : %.2f" (celsiusKeKelvin celsius)
text Keluaran
Celsius    : 25.0
Fahrenheit : 77.0
Kelvin     : 298.15

dotnet fsi pada mode skrip tidak menggemakan nilai ekspresi terakhir, sehingga nilai yang ingin ditampilkan dinyatakan secara eksplisit dengan printfn, seperti pada tiga baris terakhir berkas tersebut. Penentu %.1f dan %.2f membulatkan tampilan bilangan desimal ke satu dan dua tempat, sesuai kebutuhan masing-masing baris. Pemanggilan fungsi konversi ditulis di dalam argumen printfn, sehingga hasilnya tercetak langsung tanpa perlu diikat terlebih dahulu.

Statistik nilai dengan modul List#

Skrip kedua mengumpulkan statistik sederhana atas nilai ujian dan memperlihatkan fungsi modul List yang menjadi pola kerja sehari-hari. Angka pada datanya sengaja dipilih kecil agar setiap agregat dapat dihitung ulang dengan mudah. Berkasnya disimpan sebagai statistik-nilai.fsx.

fsharp statistik-nilai.fsx
// Statistik sederhana atas nilai ujian.
let nilai = [ 78; 85; 90; 64; 92; 71; 88 ]

let jumlah = List.sum nilai
let rataRata = float jumlah / float (List.length nilai)
let lulus = List.filter (fun n -> n >= 75) nilai

printfn "nilai     : %A" nilai
printfn "jumlah    : %d" jumlah
printfn "rata-rata : %.2f" rataRata
printfn "tertinggi : %d" (List.max nilai)
printfn "terendah  : %d" (List.min nilai)
printfn "lulus     : %d nilai" (List.length lulus)
text Keluaran
nilai     : [78; 85; 90; 64; 92; 71; 88]
jumlah    : 568
rata-rata : 81.14
tertinggi : 92
terendah  : 64
lulus     : 5 nilai

Fungsi List.sum, List.max, dan List.min menghitung agregat, sedangkan List.filter menyaring nilai yang memenuhi syarat sebelum dihitung jumlahnya. Konversi float pada perhitungan rata-rata diperlukan karena pembagian dua bilangan bulat pada F# menghasilkan bilangan bulat. Penentu %A mencetak list utuh dengan pemisah titik koma, mengikuti gaya penulisan list F# itu sendiri.

Sesi interaktif dengan REPL#

Perintah dotnet fsi tanpa nama berkas membuka sesi interaktif yang disebut REPL (Read-Eval-Print Loop), yaitu siklus membaca masukan, mengevaluasinya, lalu mencetak hasilnya. Berbeda dengan mode satu kali, sesi ini menyimpan seluruh pengikat yang sudah dibuat sehingga cocok untuk menguji ide secara bertahap. Setiap masukan diakhiri tanda ;; untuk dievaluasi, nilai ekspresi terakhir otomatis terikat pada nama it, dan sesi ditutup dengan #quit;;.

Transkrip berikut memperlihatkan satu sesi singkat. Setiap baris masukan diketik satu per satu, dan respons fsi muncul segera setelah ;; ditekan. Banner pembuka yang mencantumkan versi fsi dipangkas agar transkripnya ringkas.

text Sesi interaktif singkat (banner pembuka dipangkas)
> let x = 7;;
val x: int = 7

> x * 6;;
val it: int = 42

> let s = "FS" + "X";;
val s: string = "FSX"

> #quit;;

Baris berawalan > adalah masukan yang diketik, sedangkan baris di bawahnya adalah respons fsi. Pada masukan kedua, hasil ekspresi terikat pada it karena berupa nilai, bukan pengikat let baru. Nama it digunakan ulang untuk setiap ekspresi, sehingga nilai yang tersimpan selalu milik hasil terakhir.

Tabel berikut merangkum perbedaan kedua mode. Keduanya memanggil alat yang sama, sehingga perpindahan antara keduanya hanya soal cara memberi masukan.

Aspekdotnet fsi berkas.fsxdotnet fsi
Masukanseluruh berkas dijalankan dari atas ke bawahsatu ekspresi per masukan, diakhiri ;;
Statetidak tersisa setelah proses keluarterjaga selama sesi berlangsung
Pengakhiranotomatis setelah baris terakhir#quit;; atau tombol Ctrl+D
Kecocokanmenjalankan eksperimen yang sudah ditulismenguji ide baris demi baris

Memuat berkas lain dengan #load#

Direktif #load memuat dan mengevaluasi berkas .fsx lain sebelum sisa skrip dijalankan, sehingga kode bersama tidak perlu disalin ulang. Konstruksi pada berkas yang dimuat berada di dalam modul yang namanya diambil dari nama berkasnya, atau dari deklarasi modul yang ditulis di berkas tersebut, dan modul itu dibuka dengan open sebelum isiannya digunakan tanpa awalan. Berkas berikut menyimpan daftar petugas piket beserta fungsinya, dengan isiannya berada di modul Piket mengikuti nama berkasnya.

fsharp piket.fsx
// Berkas bantuan jadwal piket: daftar petugas dan pemilih petugas.
// Isiannya berada di modul Piket, mengikuti nama berkas ini.
let daftarPetugas = [ "Ali"; "Budi"; "Citra"; "Dewi" ]

let petugasHari hari =
    List.item ((hari - 1) % List.length daftarPetugas) daftarPetugas

Berkas kedua memuat berkas bantuan tersebut lalu menyusun jadwal untuk tujuh hari. Blok berikut ditandai tidak-dijalankan karena keberhasilannya bergantung pada berkas tetangganya, sehingga pelaksanaannya ditunjukkan melalui perintah terminal di bawahnya. Berkas bantuan piket.fsx sendiri tidak mencetak apa pun, sehingga bloknya tampil tanpa blok keluaran.

fsharp jadwal-piket-lengkap.fsx
// tidak-dijalankan mandiri: skrip ini memuat piket.fsx dari folder yang sama
// Menyusun jadwal piket untuk satu pekan dengan memuat berkas bantuan terlebih dahulu.
#load "piket.fsx"
open Piket

for hari in 1 .. 7 do
    printfn "hari %d: %s" hari (petugasHari hari)

Karena skrip merujuk berkas tetangganya, jalankan dari folder yang memuat keduanya. Perintahnya identik dengan skrip lain, hanya isi folder kerja yang menentukan berhasil atau tidaknya pemuatan.

bash
# jalankan dari folder tempat kedua berkas berada
dotnet fsi jadwal-piket-lengkap.fsx
text Keluaran
hari 1: Ali
hari 2: Budi
hari 3: Citra
hari 4: Dewi
hari 5: Ali
hari 6: Budi
hari 7: Citra

Direktif #load boleh dituliskan lebih dari satu dalam satu skrip, dan urutan pemuatannya mengikuti urutan penulisannya. Berkas yang dimuat dievaluasi dengan aturan yang sama, termasuk seluruh direktif yang ada di dalamnya.

Merujuk paket NuGet dengan #r#

Direktif #r merujuk assembly dari luar skrip, baik berkas .dll maupun paket NuGet. Bentuk untuk paket NuGet adalah #r "nuget: NamaPaket" dengan versi opsional setelah tanda koma. Mekanisme ini tersedia secara bawaan pada fsi sejak F# 5. Paket yang dirujuk diunduh saat penggunaan pertama, kemudian disimpan dalam cache NuGet untuk pemanggilan berikutnya, sehingga pemanggilan pertama memerlukan koneksi jaringan dan waktunya bergantung pada ukuran paket beserta dependensinya.

fsharp Contoh: merujuk paket NuGet dari skrip
// tidak-dijalankan
// pola umum: merujuk paket, membuka namespace, lalu menggunakan tipenya
#r "nuget: FSharp.Data"
open FSharp.Data

Potongan di atas hanya memperlihatkan polanya, dan paket FSharp.Data dipilih sebagai contoh karena menyediakan tipe untuk mengakses data JSON dan CSV. Setelah direktifnya diproses, namespace-nya dibuka dengan open, lalu tipe dari paket tersebut digunakan seperti tipe biasa. Selama cache NuGet sudah berisi versi paket yang sama, pemanggilan berikutnya tidak menyentuh jaringan lagi.

Pembeda kedua direktif ini sederhana, yaitu #load ditujukan untuk berkas .fsx yang ditulis sendiri, sedangkan #r ditujukan untuk assembly pihak luar. Berkas .dll lokal juga dirujuk dengan #r langsung ke jalurnya, sedangkan #load hanya menerima berkas skrip.

Ringkasan#

  • dotnet fsi nama.fsx mengeksekusi seluruh berkas lalu keluar, sedangkan dotnet fsi membuka sesi REPL dengan state yang terjaga.
  • Mode skrip tidak menggemakan nilai ekspresi, sehingga keluaran dinyatakan dengan printfn.
  • #load memuat berkas .fsx lain yang isiannya diakses melalui modul berkas tersebut, sedangkan #r "nuget: ..." merujuk paket NuGet dengan cache setelah pemanggilan pertama.

Referensi#