Proc dan nilai kembalian berganda

Prosedur dengan hasil bernama dan pengembalian multi-nilai.

Daftar isi

Sebuah proc (procedure), yaitu langkah pemrograman yang diberi nama dan dapat dipanggil ulang, menetapkan kontrak keluarannya melalui daftar nilai kembalian. Topik ini membahas cara mendeklarasikan proc dengan parameter dan nilai kembalian tunggal, perbedaan penerusan parameter sebagai salinan dan melalui alamat memori, pengembalian berganda dengan hasil bernama, serta penguraian hasil pemanggilan ke dalam variabel. Contoh yang dijalankan mencakup pembagian bulat yang menyertakan status keberhasilan, pemilihan nilai minimum dan maksimum, dan perhitungan statistik dua nilai. Setelah topik ini dibaca, kontrak keluaran sebuah proc dapat ditulis dalam bentuk yang tepat untuk setiap kebutuhan.

Deklarasi proc dan nilai kembalian#

Deklarasi proc memuat nama yang diikuti operator ::, kata proc, daftar parameter beserta tipenya, dan tipe kembalian yang ditulis setelah tanda ->. Beberapa parameter yang bertipe sama dapat dinyatakan sekaligus dengan pemisah koma, seperti a, b: int pada fragmen berikut. Pernyataan return mengirim nilai kembalian kepada pemanggil sekaligus menghentikan proc pada baris tersebut.

odin Bentuk umum deklarasi proc dengan satu nilai kembalian
// tidak-dijalankan

// proc dengan dua parameter sejenis dan satu nilai kembalian
jumlahkan :: proc(a, b: int) -> int {
	return a + b
}

Proc tanpa nilai kembalian cukup ditulis tanpa tanda ->, sebagaimana main :: proc() yang menjadi pintu masuk setiap program Odin. Tanda kurung daftar parameter tetap ditulis walaupun kosong, dan urutan parameter pada deklarasi menentukan urutan argumen pada pemanggilan. Operator :: pada deklarasi ini mengikat nama kepada proc, dan tanda itu berbeda dari := yang menetapkan nilai kepada variabel. Pemanggilan proc ditulis dengan nama proc diikuti argumen di dalam tanda kurung, misalnya jumlahkan(24, 8) yang menghasilkan satu nilai bertipe int. Proc pada pustaka resmi Odin ditulis dengan gaya snake_case, contohnya read_entire_file pada package core:os.

Perilaku parameter menentukan apakah proc dapat mengubah data milik pemanggilnya, dan pembahasannya berlanjut pada bagian berikut sebelum program utuh disajikan.

Parameter by value dan parameter pointer#

Semua parameter proc diteruskan by value, yaitu proc menerima salinan dari nilai argumennya, sehingga pengubahan parameter di dalam proc tidak memengaruhi variabel milik pemanggil. Penyalinan berlaku untuk tipe dasar maupun struct, sehingga parameter bertipe struct menerima salinan seluruh isinya. Konsekuensinya, proc yang hanya membaca data bekerja dengan aman pada salinan tersebut, sedangkan proc yang perlu memperbarui data pemanggil memerlukan mekanisme lain. Mekanisme itu adalah pointer, yaitu variabel yang menyimpan alamat memori dari sebuah nilai.

Tipe pointer ditulis dengan tanda ^ di depan tipe yang dituju, misalnya ^int. Tanda & di depan variabel mengambil alamatnya, sedangkan tanda ^ setelah variabel pointer mengakses nilai yang tersimpan di alamat tersebut. Proc naikkan pada program di bagian berikutnya menerapkan pola ini dengan parameter n: ^int dan operasi n^ += sebesar, sehingga yang diperbarui adalah variabel milik pemanggil, bukan salinannya. Parameter sebesar pada proc yang sama tetap diteruskan sebagai salinan karena proc itu hanya membacanya, sehingga dua gaya penerusan berdampingan dalam satu daftar parameter.

Pengembalian berganda dengan hasil bernama#

Tipe kembalian yang ditulis di dalam tanda kurung memuat dua nilai atau lebih dengan tipe yang boleh berbeda, misalnya -> (int, f64). Proc bagi pada program berikut mengembalikan int dan bool sekaligus, yaitu hasil pembagian bulat dan status keberhasilannya, sedangkan min_max mengembalikan dua nilai sejenis dalam urutan kecil lalu besar. Proc statistik mengembalikan jumlah dan rata-rata dari satu perulangan, sehingga pemanggil tidak perlu menjalankan dua proc terpisah untuk data yang satu paket.

Setiap nilai kembalian dapat diberi nama, misalnya -> (jumlah: int, rata_rata: f64). Hasil bernama berperilaku seperti variabel lokal yang sudah berisi nilai nol, sehingga dapat diisi bertahap tanpa deklarasi tambahan. Pernyataan return tanpa daftar nilai mengembalikan seluruh hasil bernama sebagaimana adanya, sedangkan bentuk eksplisit seperti return a / b, true wajib menyertakan seluruh nilai sekaligus.

Program berikut memuat seluruh proc yang dibahas sampai titik ini beserta pemanggilannya.

odin main.odin
package main

import "core:fmt"

// bagi mengembalikan hasil pembagian bulat beserta status keberhasilannya
bagi :: proc(a, b: int) -> (hasil: int, ok: bool) {
	if b == 0 {
		return 0, false
	}
	return a / b, true
}

// min_max mengurutkan dua nilai menjadi pasangan (kecil, besar)
min_max :: proc(a, b: int) -> (kecil, besar: int) {
	if a <= b {
		return a, b
	}
	return b, a
}

// statistik mengisi dua hasil bernama dalam satu perulangan,
// lalu kembali tanpa menyebut nilai apa pun
statistik :: proc(nilai: []int) -> (jumlah: int, rata_rata: f64) {
	for n in nilai {
		jumlah += n
	}
	rata_rata = f64(jumlah) / f64(len(nilai))
	return
}

// naikkan mengubah nilai di luar proc melalui pointer
naikkan :: proc(n: ^int, sebesar: int) {
	n^ += sebesar
}

// dobel_bagi meneruskan kegagalan bagi ke pemanggilnya melalui or_return
dobel_bagi :: proc(a, b: int) -> (hasil: int, ok: bool) {
	nilai := bagi(a, b) or_return
	return nilai * 2, true
}

main :: proc() {
	kecil, besar := min_max(24, 7)
	fmt.printfln("min = %d, max = %d", kecil, besar)

	hasil, ok := bagi(24, 8)
	fmt.printfln("24 / 8 = %d, ok = %t", hasil, ok)

	// nilai kedua yang tidak dipakai dibuang ke _
	_, ada := bagi(24, 0)
	fmt.printfln("24 / 0 menghasilkan nilai: %t", ada)

	ganda, ganda_ok := dobel_bagi(24, 8)
	fmt.printfln("dobel_bagi(24, 8): hasil = %d, ok = %t", ganda, ganda_ok)

	ganda, ganda_ok = dobel_bagi(24, 0)
	fmt.printfln("dobel_bagi(24, 0): hasil = %d, ok = %t", ganda, ganda_ok)

	nilai := 10
	naikkan(&nilai, 5)
	fmt.printfln("nilai = %d", nilai)

	jumlah, rata := statistik([]int{72, 85, 90, 68, 77})
	fmt.printfln("jumlah = %d, rata-rata = %.1f", jumlah, rata)
}
bash
# sumberkan lingkungan sandbox jalur odin bila belum tersedia
[ -f /home/kal/my-project/tools/env.sh ] && source /home/kal/my-project/tools/env.sh
# jalankan program dari dalam folder topik
odin run main.odin -file
text Keluaran
min = 7, max = 24
24 / 8 = 3, ok = true
24 / 0 menghasilkan nilai: false
dobel_bagi(24, 8): hasil = 6, ok = true
dobel_bagi(24, 0): hasil = 0, ok = false
nilai = 15
jumlah = 392, rata-rata = 78.4

Keluaran program memperlihatkan bahwa setiap kontrak bekerja sesuai deklarasinya. Pemanggilan min_max(24, 7) mengembalikan pasangan terurut 7 dan 24, pemanggilan bagi(24, 0) menyerahkan false sebagai status tanpa menghitung hasilnya, dan naikkan(&nilai, 5) mengubah variabel pemanggil melalui alamatnya. Proc statistik menghasilkan 392 dan 78.4 dari lima nilai ujian, angka yang mudah dihitung ulang untuk memastikan kedua nilai kembaliannya benar.

Destrukturisasi hasil dan idiom or_return#

Hasil pemanggilan berganda diuraikan ke dalam variabel melalui := dengan jumlah variabel yang sama dengan jumlah nilai kembaliannya, misalnya kecil, besar := min_max(24, 7). Bentuk penguraian ini dikenal sebagai destructuring, dan variabel yang dihasilkan adalah variabel biasa, sehingga pemanggilan berikutnya cukup menggunakan = seperti pada dua pemanggilan dobel_bagi berturut-turut. Nilai yang tidak dipakai dibuang ke _, sebagaimana _, ada := bagi(24, 0) yang menyimpan statusnya saja.

Idiom or_return meringkas pola pemeriksaan status yang berulang, yaitu menaruh hasil pemanggilan ke variabel lalu mengembalikan kegagalan ke pemanggil apabila statusnya menandakan gagal. Baris nilai := bagi(a, b) or_return pada proc dobel_bagi melakukan dua hal sekaligus, yaitu mengisi nilai bila pembagian berhasil dan mengembalikan false kepada pemanggil dengan nilai kembalian lainnya bernilai nol bila gagal. Keluaran program memperlihatkan kedua jalurnya, yaitu hasil 6 untuk dobel_bagi(24, 8) dan 0 beserta false untuk dobel_bagi(24, 0). Pola yang sama banyak dipakai oleh proc pustaka core, sehingga bentuk ini sering dijumpai saat membaca kode pustaka.

Kesalahan umum#

Kesalahan pertama adalah memakai nilai pertama tanpa memeriksa status pada nilai keduanya. Pemanggilan hasil, _ := bagi(24, 0) tidak memicu error apa pun, tetapi hasil bernilai 0, nilai yang menyesatkan karena pembagiannya tidak pernah dihitung. Status ok atau ada diperiksa terlebih dahulu sebelum nilai pertama dipakai, dan jalur kegagalan cukup dilaporkan tanpa melanjutkan perhitungan.

Kesalahan kedua adalah menyertakan sebagian nilai saja pada pernyataan return eksplisit. Pada proc dengan dua nilai kembalian, baris return a / b ditolak compiler dengan pesan yang menyebut jumlah nilai yang diharapkan, misalnya Expected 2 return values, got 1. Perbaikannya berupa pengembalian seluruh nilai, atau pemakaian return tanpa nilai bila semua hasil bernama sudah terisi.

Kesalahan ketiga adalah mengubah parameter bertipe biasa lalu mengharapkan variabel pemanggil ikut berubah. Parameter diteruskan sebagai salinan, sehingga penetapan nilai seperti n = 0 di dalam proc hanya menyentuh salinan lokalnya. Proc yang perlu memperbarui data pemanggil menerima parameter pointer, dan pemanggil menyerahkan alamatnya melalui &, sebagaimana pola naikkan pada program utuh.

Ringkasan#

  • Proc dideklarasikan dengan nama :: proc(param: tipe) -> tipe, dan semua parameter diteruskan sebagai salinan kecuali parameternya bertipe pointer.
  • Pengembalian berganda ditulis dalam tanda kurung dengan tipe yang boleh berbeda, sedangkan hasil bernama dapat diisi bertahap dan dikembalikan dengan return tanpa nilai.
  • Hasil pemanggilan diuraikan ke variabel melalui :=, dan _ membuang nilai yang tidak dipakai.
  • Idiom or_return meneruskan kegagalan ke pemanggil secara langsung dan menuntut hasil bernama pada proc yang memuatnya.

Referensi#