Konteks

Konteks scalar dan list sebagai mental model utama yang menjelaskan mengapa ekspresi yang sama berperilaku berbeda.

Daftar isi

Konteks adalah keadaan yang menentukan bentuk hasil sebuah ekspresi, dan menjadi mental model utama untuk membaca perilaku Perl. Ekspresi yang sama dapat menghasilkan satu nilai pada satu tempat dan deretan nilai pada tempat lain, tanpa ada perubahan pada ekspresinya. Topik ini membahas dua konteks utama, yaitu konteks scalar dan konteks list, beserta contohnya pada array, fungsi localtime, dan pembacaan STDIN.

Dua jenis konteks#

Perl mengevaluasi setiap ekspresi di dalam konteks tertentu. Apabila pemakai ekspresi menuntut satu nilai, ekspresi tersebut berada dalam konteks scalar. Apabila pemakai menuntut deretan nilai, ekspresi tersebut berada dalam konteks list. Penugasan ke variabel skalar menetapkan konteks scalar, sedangkan penugasan ke array menetapkan konteks list.

Konteks ditetapkan oleh pihak yang menggunakan hasil, bukan oleh ekspresinya sendiri. Karena itu ekspresi yang persis sama dapat berperilaku berbeda pada dua tempat. Diagram berikut merangkum hubungan tersebut untuk sebuah array bernama @sensor:

Konteks scalar dan list pada sebuah array@sensorekspresi yang samaKonteks scalar$jumlah = @sensor;satu nilai, yaitu jumlah elemenKonteks listmy @salinan = @sensor;seluruh elemen dalam urutan semula
Ekspresi yang sama berperilaku berbeda menurut konteks

Kedua cabang pada diagram menggunakan ekspresi @sensor yang sama persis. Perbedaannya hanya ada pada sisi kiri penugasan, dan di situlah konteks ditetapkan. Menyimpan hasil ke $jumlah menuntut satu nilai, sedangkan menyimpan hasil ke @salinan menuntut seluruh deretan, dan array menyesuaikan diri dengan tuntutan tersebut.

Array dalam konteks scalar dan list#

Array ditandai tanda @ dan menyimpan deretan nilai yang diberi nomor urut. Satu sifat array perlu dipahami sejak sekarang, yaitu bentuk sumbangannya berubah menurut konteks. Skrip berikut menunjukkan ketiga bentuknya:

perl konteks.pl
#!/usr/bin/env perl
use strict;
use warnings;

my @sensor = (21.5, 22, 20.8, 21.1);

# konteks scalar: array menyumbang jumlah elemennya
my $jumlah = @sensor;
print "Jumlah elemen: $jumlah\n";

# scalar() memaksa konteks scalar secara eksplisit
print "Ukuran array: ", scalar(@sensor), "\n";

# konteks list: array menyumbang seluruh elemennya
my @salinan = @sensor;
print "Isi salinan: @salinan\n";
text Keluaran
Jumlah elemen: 4
Ukuran array: 4
Isi salinan: 21.5 22 20.8 21.1

Penugasan ke $jumlah menetapkan konteks scalar, sehingga @sensor menyumbang jumlah elemennya, yaitu 4. Fungsi scalar() melakukan hal yang sama secara eksplisit pada tempat yang tidak menetapkan konteks sendiri, misalnya daftar argumen print. Pada baris ketiga, penugasan ke @salinan menetapkan konteks list, sehingga array menyumbang seluruh elemennya dalam urutan semula.

Satu tanda baca pada sisi kiri penugasan mengubah hasilnya secara mendasar. Penugasan my ($pertama) = @sensor; menetapkan konteks list karena tanda kurungnya, sehingga $pertama berisi elemen pertama array, yaitu 21.5. Tanpa tanda kurung, penugasan yang tampak serupa menyimpan jumlah elemennya, sehingga keberadaan tanda kurung wajib dibaca dengan cermat.

Penugasan $jumlah = @sensor dan pemanggilan scalar(@sensor) memberikan hasil yang sama, dan keduanya merupakan idiom yang muncul terus-menerus pada skrip Perl. Bentuk penugasan lebih ringkas untuk penghitung, sedangkan bentuk scalar() lebih jelas ketika array berada di tengah argumen lain. Kasus lain yang sering mengejutkan adalah print @sensor, yang mencetak seluruh elemen tanpa pemisah karena daftar argumen print berada dalam konteks list. Apabila jumlah elemen yang dikehendaki di tengah argumen cetak, scalar(@sensor) wajib ditulis secara eksplisit.

localtime dalam dua konteks#

Fungsi localtime memperlihatkan perubahan bentuk hasil secara jelas. Pada konteks scalar, localtime menghasilkan satu string waktu yang mudah dibaca. Pada konteks list, fungsi yang sama menghasilkan sembilan komponen terpisah, mulai dari detik hingga penanda waktu musim panas.

Keluaran localtime bergantung pada waktu berjalan, sehingga contoh di sini memanggilnya dengan argumen 0. Angka tersebut berarti titik waktu epoch, yaitu nol detik sejak 1 Januari 1970, dan zona waktu dipaksa menjadi UTC agar keluarannya tetap dan dapat direproduksi:

bash
# localtime dalam konteks scalar pada titik waktu epoch nol
TZ=UTC perl -e 'use strict; use warnings; my $waktu = localtime 0; print "$waktu\n"' 2>&1
text Keluaran
Thu Jan  1 00:00:00 1970
bash
# localtime dalam konteks list pada titik waktu yang sama
TZ=UTC perl -e 'use strict; use warnings; my @komponen = localtime 0; print "@komponen\n"' 2>&1
text Keluaran
0 0 0 1 0 70 4 0 0

Daftar tersebut dibaca sebagai 0 detik, 0 menit, 0 jam, tanggal 1, bulan 0, tahun 70, hari keempat dalam minggu, hari pertama dalam tahun, dan penanda waktu musim panas bernilai 0. String pada konteks scalar dan daftar pada konteks list memuat informasi yang sama, hanya bentuknya yang berbeda. Pemanggilan tanpa argumen menggunakan waktu berjalan saat itu, sedangkan argumen berupa jumlah detik sejak epoch digunakan di sini agar keluarannya dapat direproduksi. Fungsi gmtime bekerja dengan cara yang sama persis, tetapi mengabaikan zona waktu setempat dan menghasilkan waktu universal.

STDIN dalam dua konteks#

Operator <STDIN> membaca baris dari masukan standar. Pada konteks scalar, <STDIN> menghasilkan satu baris berikutnya, lengkap dengan karakter baris baru di ujungnya. Pada konteks list, <STDIN> menghasilkan seluruh baris yang tersisa sampai masukan berakhir. Masukan contoh dialirkan melalui pipe agar keluarannya dapat direproduksi:

bash
# konteks scalar membaca satu baris pertama saja
printf "satu\ndua\ntiga\n" | perl -e 'use strict; use warnings; my $baris = <STDIN>; print "Terbaca: [$baris]"' 2>&1
text Keluaran
Terbaca: [satu
]

Tanda kurung siku memperlihatkan batas teks yang terbaca. Penutup kurung jatuh ke baris kedua karena baris masukan membawa karakter baris barunya sendiri, dan karakter itu ikut tersimpan dalam $baris. Baris baru seperti ini perlu dipangkas dengan chomp sebelum teksnya digabungkan ke pesan lain. Apabila pembacaan dilakukan setelah masukan habis, <STDIN> menghasilkan undef, dan pemakaian variabelnya memunculkan peringatan Use of uninitialized value.

bash
# konteks list membaca seluruh baris masukan sekaligus
printf "satu\ndua\ntiga\n" | perl -e 'use strict; use warnings; my @baris = <STDIN>; print "Terdapat ", scalar(@baris), " baris\n"' 2>&1
text Keluaran
Terdapat 3 baris

Ketiga baris masukan terbaca dalam satu pernyataan, dan scalar(@baris) menghitung isinya menjadi 3. Kombinasi penugasan ke array dan scalar() ini menjadi cara baku untuk menentukan banyaknya baris yang diterima. Dalam konteks list, pembacaan berhenti dengan sendirinya ketika masukan berakhir, sehingga tidak ada elemen kosong yang ikut tersimpan. Setiap elemen yang tersimpan tetap membawa baris barunya, sehingga chomp pada seluruh array tetap menjadi langkah pembersihannya. Kedua konteks <STDIN> itulah alasan sebuah operator sederhana perlu dibaca dengan memperhatikan tempat hasilnya digunakan.

Kesalahan umum#

  1. Tanda kurung pada penugasan array dilupakan atau berlebihan. Penugasan my $jumlah = @sensor; menyimpan jumlah elemen, sedangkan my ($jumlah) = @sensor; menyimpan elemen pertamanya. Tentukan tanda kurung sesuai nilai yang dikehendaki sebelum program dijalankan.
  2. Komponen localtime dicetak mentah. Bulan terhitung dari 0 dan tahun terhitung sejak 1900, sehingga Januari tercetak sebagai 0 dan tahun 1970 tercetak sebagai 70. Tambahkan 1 pada komponen bulan dan 1900 pada komponen tahun saat keduanya ditampilkan.
  3. <STDIN> dalam konteks list dijalankan tanpa masukan sehingga program tampak menggantung. Program tidak mengalami error, melainkan menunggu seluruh baris sampai masukan berakhir. Uji melalui pipe, atau tutup masukan dari terminal dengan tombol penanda akhir berkas (end of file), yaitu Ctrl-D pada terminal Linux.

Ringkasan#

  • Konteks ditetapkan oleh pemakai hasil ekspresi, bukan oleh ekspresinya sendiri.
  • @array pada konteks scalar menyumbang jumlah elemennya, sedangkan pada konteks list menyumbang seluruh elemennya dalam urutan semula.
  • scalar() memaksa konteks scalar secara eksplisit, dan penugasan ke variabel skalar adalah bentuk paling lazimnya.
  • localtime menghasilkan string pada konteks scalar dan sembilan komponen pada konteks list, sedangkan <STDIN> menghasilkan satu baris atau seluruh baris masukan.

Referensi#