exists dan delete

Menguji keanggotaan kunci hash dengan exists dan menghapus entri dengan delete tanpa memicu peringatan.

Daftar isi

Keanggotaan dan penghapusan entri hash dikelola oleh dua fungsi berpasangan, yaitu exists untuk menguji keberadaan kunci dan delete untuk menghapus entrinya. Keduanya penting karena hash boleh menyimpan kunci dengan nilai undef, sehingga sekadar membaca nilai tidak cukup untuk mengetahui apakah sebuah kunci tercatat. Topik ini membahas perbedaan antara kunci yang tidak ada, kunci bernilai undef, dan nilai yang belum terisi, beserta peringatan yang muncul ketika nilai undef ikut dicetak.

Menguji keanggotaan kunci dengan exists#

Fungsi exists $hadir{Budi} menguji apakah kunci Budi tercatat pada hash, tanpa memandang isi nilainya. Hasilnya berupa nilai benar atau salah yang langsung digunakan pada percabangan if. Bentuk kebalikannya ditulis if (not exists ...), sebagaimana pemeriksaan kunci Dewi pada skrip berikut.

Pembeda yang penting adalah kunci yang nilainya undef. Penugasan $hadir{Eko} = undef tetap mencatat kunci Eko, sehingga exists tetap menghasilkan benar, sedangkan fungsi defined menguji nilainya dan menghasilkan salah. Dengan demikian, exists menjawab pertanyaan apakah kunci tercatat, sedangkan defined menjawab pertanyaan apakah nilai kunci tersebut terisi. Keduanya saling melengkapi, dan penggunaan keduanya secara tertukar merupakan sumber kesalahan yang halus pada program yang datanya berasal dari luar skrip. Nilai 1 pada hash kehadiran hanyalah penanda bahwa peserta tercatat, dan isi angkanya tidak digunakan dalam pengujian.

Pengujian exists dan pembacaan nilai sama-sama tidak menciptakan entri baru, sehingga hash tetap utuh setelah keduanya diterapkan pada kunci yang belum tercatat. Entri baru hanya muncul pada saat penulisan nilai, sebagaimana penugasan undef pada skrip di atas yang mencatat kunci Eko. Kunci hash disimpan sebagai string, sehingga nilai apa pun yang digunakan sebagai kunci dikonversi menjadi teks lebih dahulu. Bentuk negasinya juga tidak terbatas pada not, karena if (! exists $hadir{Dewi}) memberikan hasil yang sama, dan pemilihan bentuknya merupakan soal konsistensi gaya dalam satu skrip.

perl keanggotaan.pl
use strict;
use warnings;

# daftar kehadiran peserta pelatihan
my %hadir = (
    Ali   => 1,
    Budi  => 1,
    Citra => 1,
);

# exists menguji apakah kunci tercatat pada hash
if (exists $hadir{Budi}) {
    print "Budi tercatat hadir\n";
}

if (not exists $hadir{Dewi}) {
    print "Dewi tidak tercatat\n";
}

# kunci yang nilainya undef tetap dianggap ada oleh exists
$hadir{Eko} = undef;
if (exists $hadir{Eko}) {
    print "Eko tercatat, nilainya undef\n";
}

# defined menguji nilai, bukan keberadaan kunci
if (not defined $hadir{Eko}) {
    print "Nilai Eko belum terisi\n";
}
text Keluaran
Budi tercatat hadir
Dewi tidak tercatat
Eko tercatat, nilainya undef
Nilai Eko belum terisi

Nilai kunci yang tidak ada dan peringatannya#

Membaca kunci yang tidak tercatat pada hash tidak menghentikan program. Ekspresi $stok{buku} pada hash tanpa kunci buku menghasilkan undef secara diam-diam, karena pembacaan tidak dianggap sebagai kesalahan. Peringatan baru muncul ketika nilai undef itu ikut digunakan, misalnya diinterpolasi ke dalam string untuk dicetak. Dengan use warnings aktif, Perl menampilkan pesan Use of uninitialized value beserta nama variabelnya, sedangkan program tetap berjalan dan bagian yang kosong dicetak sebagai string kosong.

Peraga berikut menunjukkan peringatan tersebut pada hash persediaan yang tidak memiliki kunci buku.

bash
# peraga peringatan ketika kunci hash yang tidak ada ikut dicetak
perl -e 'use strict; use warnings; my %stok = (pensil => 24); print "Stok buku: [$stok{buku}]\n";' 2>&1
text Keluaran
Use of uninitialized value $stok{"buku"} in concatenation (.) or string at -e line 1.
Stok buku: []

Program tetap mencetak baris kedua, sehingga perilaku ini mudah luput bila keluaran tidak diperiksa. Perbaikannya bukan menghilangkan use warnings, melainkan menguji kuncinya lebih dahulu. Pencetakan langsung diganti dengan pemeriksaan exists $stok{buku} sebelum mencetak, atau dengan nilai cadangan apabila kunci tidak ada. Dengan disiplin ini, peringatan uninitialized value menjadi sinyal yang jarang muncul, sehingga setiap kemunculannya layak ditelusuri sampai akarnya.

Pesan peringatan itu memuat nama variabel lengkap dengan berkas dan baris kejadiannya, sehingga sumbernya dapat ditelusuri tanpa menebak. Frasa in concatenation (.) or string muncul karena interpolasi string dikompilasi sebagai penyambungan, sehingga Perl melaporkannya pada operasi yang sama. Untuk memberi nilai cadangan tanpa percabangan, operator defined-or // mengembalikan nilai di sebelah kanannya apabila nilai di sebelah kirinya belum terisi. Penulisan $stok{buku} // 0 menghasilkan 0 alih-alih memicu peringatan. Operator ini berbeda dari ||, karena || juga mengganti nilai 0 dan string kosong yang sah, sedangkan // hanya bereaksi atas undef.

Menghapus entri dengan delete#

Fungsi delete $stok{penghapus} menghapus pasangan kunci dan nilai sekaligus, lalu mengembalikan nilai yang terhapus. Setelah penghapusan, kunci tersebut tidak lagi ditemukan oleh exists, dan scalar(keys %stok) berkurang satu. Perilaku ini berbeda dengan penugasan $stok{penghapus} = undef, yang hanya mengosongkan nilai tetapi tetap meninggalkan kuncinya tercatat. Apabila maksudnya benar-benar mencabut entri, delete merupakan pilihannya.

delete pada kunci yang memang tidak tercatat tidak memicu peringatan dan hanya mengembalikan undef, sehingga penghapusan atas kunci yang mungkin sudah tidak ada dapat ditulis tanpa pengujian lebih dahulu. Pada skrip berikut, kunci penggaris tidak pernah ada, dan hasil delete-nya diuji dengan defined agar keluarannya menampilkan kata undef.

perl hapus.pl
use strict;
use warnings;

# persediaan alat tulis
my %stok = (
    pensil    => 24,
    buku      => 10,
    penghapus => 7,
);

# delete menghapus entri dan mengembalikan nilai yang terhapus
my $dihapus = delete $stok{penghapus};
print "Nilai terhapus : $dihapus\n";

# kunci yang sudah dihapus tidak lagi ditemukan oleh exists
if (not exists $stok{penghapus}) {
    print "Entri penghapus sudah tidak ada\n";
}

# delete pada kunci yang tidak tercatat mengembalikan undef tanpa peringatan
my $hasil = delete $stok{penggaris};
print "Hasil penggaris: ", (defined $hasil ? $hasil : "undef"), "\n";
print "Banyak entri   : ", scalar(keys %stok), "\n";
text Keluaran
Nilai terhapus : 7
Entri penghapus sudah tidak ada
Hasil penggaris: undef
Banyak entri   : 2

Nilai balik penghapusan berguna untuk pencatatan, misalnya menyimpan nilai yang dicabut sebelum entrinya dihilangkan dari hash. Setelah kedua operasi pada skrip, hash hanya memuat kunci pensil dan buku, sebagaimana terlihat pada penghitungan terakhirnya. Pola uji exists sebelum penghapusan tetap sah, tetapi menjadi pilihan gaya dan bukan keharusan, karena delete sendiri sudah aman pada kunci yang tidak ada.

Kesalahan umum#

  1. Menggunakan pembacaan nilai sebagai uji keanggotaan kunci. Kunci yang nilainya undef tetap tercatat, sehingga if ($hadir{Eko}) memperlakukan kunci tercatat itu sama dengan kunci yang tidak ada. Perbaikannya menggunakan exists $hadir{Eko} apabila yang ditanya adalah keberadaan kunci, dan defined $hadir{Eko} apabila yang ditanya adalah isi nilainya.
  2. Mencetak nilai kunci yang belum terisi tanpa pemeriksaan terlebih dahulu. Nilai undef yang diinterpolasi memicu peringatan Use of uninitialized value, sedangkan program tetap berjalan dengan bagian kosong yang mudah luput. Perbaikannya menguji kunci dengan exists lebih dahulu atau menyediakan nilai cadangan melalui operator //.
  3. Mengira penugasan undef mencabut entri hash. Kunci tetap tercatat setelah $stok{penghapus} = undef, sehingga exists tetap benar dan scalar(keys %stok) tidak berkurang. Perbaikannya menggunakan delete $stok{penghapus} apabila entrinya harus benar-benar dihapus dari hash.

Ringkasan#

  • exists $hash{kunci} menguji keberadaan kunci dan tetap benar untuk kunci yang nilainya undef.
  • defined menguji isi nilai, sehingga kunci yang tercatat tetapi belum terisi dapat dibedakan dari kunci yang tidak ada.
  • Membaca kunci yang tidak ada menghasilkan undef tanpa peringatan, sedangkan pencetakannya memicu peringatan uninitialized value.
  • delete menghapus entri beserta mengembalikan nilainya, tidak memicu peringatan pada kunci yang tidak ada, dan berbeda dari penugasan undef.

Referensi#