9 Format Khusus
9.1 Konsep Dasar
Mempertimbangkan definisi berikut dari standard library yang mengimplementasikan semacam sederhana ungkapan bersyarat / conditional expression (lihat juga Bagian 11.8 [Kondisi Bersyarat dan Pengertian (Conditionals and Comprehensions)], halaman 127):
ifelse P X Y = X if P; = Y otherwise;
fungsi ifelse mengambil argument pertamanya suatu nilai kebenaran /truth value yang digunakan untuk menentukan apakah argument yang kedua atau ketiga akan dikembalikan sebagai nilai dari ungkapan bersyarat / conditional expression. Walaupun definisi di atas dengan sempurna benar, menggunakan evaluasi pesanan applicative dengan definisi ini adalah jelas tidak sesuai karena semua argument harus telah pernah dievaluasi sebelum fungsi ifelse dapat diberlakukan untuk [mereka/nya]. Karena baik yang kedua maupun argument ketiga sederhananya dibuang-buang, usaha dilibatkan di dalam evaluasi dari argument ini disia-siakan. Sebagai suatu kasus ekstrim, e.g., Y tidak boleh mempunyai suatu evaluasi pengakhiran (terminating evaluation) sama sekali, dalam hal mana evaluasi ifelse P X Y tidak akan ber/mengakhiri yang manapun bahkan Y benar-benar tidak diperlukan jika P terjadi untuk mengevaluasi ke true.
Sebagai gantinya, kita bermaksud mempunyai ifelse untuk mengevaluasi argumentnya hanya sebagaimana adanya yang diperlukan. Di dalam bahasa Q, ini bisa dilakukan maupun dengan sederhananya mendeklarasikan ifelse sebagai format khusus. Semua kita harus lakukan untuk mendahului penyamaan/equations di atas dengan deklarasi berikut :
public special ifelse P X Y;
Sintaks deklarasi seperti itu diperkenalkan di Bab 5 [Deklarasi], halaman 29. Kata kunci khusus harus diikuti oleh suatu identifier fungsi dan suatu urutan simbol variabel (simbol variabel hanya melayani untuk menetapkan banyaknya argument, dan jika tidak diperlakukan sebagai komentar). Jika list/daftar argument dihilangkan, simbol fungsi benar-benar diperlakukan sebagai suatu simbol biasa (tidak khusus). Jika tidak argument yang diberi dideklarasikan sebagai argument khusus (a.k.a. pemanggilan dengan nama (call by name)), yaitu, argument yang diperlakukan sebagai harafiah/literals dan karenanya adalah tidak dievaluasi ketika fungsi yang diberi diberlakukan bagi [mereka/nya]. Argument khusus akan [jadi] ditinggalkan tidak
bersesuaian disebut di (dalam) suatu konteks “tidak khusus”, interpreter akan secara otomatis
mengevaluasi [mereka/nya].
Dengan begitu deklarasi di atas dari fungsi ifelse menberitahu interpreter Q bahwa fungsi ini mengharapkan tiga argument khusus P, X dan Y yang harus ditinggalkan tidak dievaluasi sampai nilai-nilai mereka benar-benar diperlukan di dalam qualifier atau sisi right-hand dari suatu penyamaan/equation di dalam definisi ifelse. Sebagai konsekuensi, ketika interpreter datang untuk mempertimbangkan aturan yang pertama,
ifelse P X Y = X if P;
[itu] akan pertama mengevaluasi P untuk memperoleh nilai dari bagian kondisi aturan ini. Jika P mengevaluasi ke true, X akan [jadi] dievaluasi dan dikembalikan sebagai nilai dari sisi left-hand ungkapan/expression. Cara lainnya (jika P mengevaluasi ke false), X akan [jadi] ditinggalkan tidak dievaluasi, dan interpreter mempertimbangkan aturan yang berikutnya,
ifelse P X Y = Y otherwise;
penyebab untuk mengurangi ifelse P X Y kepada nilai Y.
(Catat bahwa seperti contoh di atas, di mana suatu argument khusus membentuk sisi righthand dari suatu aturan, interpreter melaksanakan tail/ekor panggil/hubungi penghapusan seperti biasanya (cf. Bagian 7.10 [Rekursi Tail/Ekor], halaman 68). Itu adalah, akan sungguh-sungguh mengevaluasi argument khusus setelah melakukan/menyelenggarakan suatu pengurangan ekor/tail dari aturan itu. Ini penting untuk fungsi berulang / rekursiv sederhana yang menyertakan format khusus seperti ifelse yang dapat dieksekusi di dalam ruang tumpukan tetap (constant stack space). Ini juga berlaku, khususnya, untuk operator khusus built-in (end then) dan (or else), lihat Bagian 9.3 [Format Khusus Built-In], halaman 88.)
Adalah penting untuk catat di sini bahwa format khusus itu tentu saja suatu feature runtime di dalam bahasa Q. Ini berarti bahwa format khusus adalah tidak hanya dikenali pada waktu compile/menyusun, tetapi juga ketika mereka dilewati sebagai argument atau kembalikan sebagai hasil fungsi (function results) selama suatu perhitungan (yang pada umumnya tidak bisa diramalkan/diprediksi pada waktu compile/ menyusun). Sebagai contoh, jika kita menggambarkan foo sebagai berikut:
special bar X;
foo = bar;
maka foo (1+1) mengevaluasi bar (1+1) dan bukannya bar 2, walaupun foo sendiri tidak dideklarasikan untuk menjadi suatu format khusus. Ini juga bekerja jika kamu melewati bar sebagai parameter fungsional:
special bar X;
foo F X = F (X+1);
Dengan definisi di atas, foo bar 1 akan mengevaluasi untuk bar (1+1).
Pada sisi lain, kamu harus berhati-hati bahwa argument untuk fungsi khusus yang diteruskan/dilewati oleh fungsi lain adalah tidak dievaluasi terlalu awal. Sebagai contoh, jika berlaku fungsi yang digambarkan sebagai berikut:
apply F X = F X;
dan kemudian dilibatkan sebagai apply bar (1+1) maka (1+1) akan dievaluasi bahkan walaupun bar adalah suatu format khusus. Alasannya adalah bahwa argument (1+1) dievaluasi sebelum apply diberlakukan bagi itu, karena kita belum mendeklarasikan apply sebagai format khusus juga. Pada umumnya, kamu perlu membuat fungsi yang merupakan suatu format khusus yang melewati/meneruskan argumentnya kepada format khusus yang lain, kecuali jika kamu dengan tegas ingin mengevaluasi argument ini.
Hingga kini, kita hanya mempertimbangkan format khusus “murni”, yang menerima semua
argument mereka tidak dievaluasi. Banyak fungsi, bagaimanapun, memerlukan suatu campuran dari argument tidak khusus dan khusus. Bahasa Q menyediakan dua metoda berbeda dalam hubungan dengan situasi seperti itu.
Pertama, kamu dapat memaksa evaluasi dari suatu argument khusus pada runtime menggunakan operator‘~’ (tilde atau “kekuatan/force”):
special foo P X;
foo P X = ifelse ~P (bar X) X;
Di sini, kondisi P dievaluasi sebelum dilewati/diteruskan ke atas ke format khusus ifelse (yang mana seperti digambarkan di atas). Metoda ini bermanfaat jika kita hanya adakalanya harus mengevaluasi suatu parameter dari suatu format khusus. (Catat bahwa kamu dapat juga
memaksa ungkapan/expressions yang (di) luar suatu format khusus; dalam hal ini ‘~’ bertindak
sebagai operasi identitas, yaitu., ungkapan/expression argument (yang telah di dalam format normal) sederhananya dikembalikan sebagaimana mestinya.)
Jika kita selalu ingin mengevaluasi parameter ditentukan dari suatu fungsi khusus, kita dapat juga mendeklarasikan argument yang yang bersesuaian sebagai tidak khusus yang secara efektif memutar argument ke dalam suatu panggilan biasa dengan parameter nilai (an ordinary call by value parameter). Ini dilakukan dengan mendahului argument dengan simbol ‘~’ di dalam pendeklarasian simbol fungsi. Sebagai contoh, dalam definisi di atas dari fungsi ifelse, argument yang pertama akan [jadi] dievaluasi bagaimanapun caranya. Karenanya kita boleh juga membuatnya suatu argument tidak khusus. Deklarasi yang bersesuaian membaca sebagai berikut:
public special ifelse ~P X Y;
Dengan deklarasi ini (yang dengan tepat bagaimana ifelse benar-benar dideklarasikan di dalam standardlibrary), fungsi ifelse selalu menerima argument pertamanya dievaluasi, selama dua argument yang lain itu khusus. Pekerjaan ini persisnya seperti suatu aplikasi yang tegas/eksplisit
dari operator ‘~’, tetapi melepaskan kamu dari beban dalam mempunyai untuk memaksa
evaluasi argumtnt di tiap-tiap aplikasi fungsi ifelse. Ini juga sedikit lebih efisien karena argument tidaklah harus dibangun sebagai expression/ungkapan harafiah/literal, tetapi dapat dievaluasi segera sebelum fungsi ifelse diberlakukan bagi itu.
Masalah potensial yang lain dengan format khusus adalah isu tentang evaluasi yang diulangi (repeated evaluations) dari ungkapan ditunda (deferred expression) yang sama. Catatlah bahwa suatu argument khusus dievaluasi setiap kali nilainya diperlukan pada sisi (right-hand) dari suatu definisi. Pempertimbangkanlah, sebagai contoh:
foo X = bar X X;
Ketika aturan ini diterapkan, X benar-benar mendapat/kan dievaluasi dua kali, sekali untuk masing-masing kejadian pada sisi (right-hand) dari equation/penyamaan. Ini mungkin adalah yang tidak diinginkan untuk pertimbangan efisiensi. Satu metoda untuk mengatasi situasi seperti itu adalah dengan memperkenalkan suatu variabel lokal yang mengikat, e.g.:
special foo X;
foo X = bar Y Y where Y = X;
Bagaimanapun, dalam beberapa hal ini mungkin jujur atau mustahil merepotkan. Khususnya, kekuatan nilai yang ditunda benar-benar disimpan di dalam suatu pembangun/constructor data khusus, seperti suatu stream. Sebagai contoh, mempertimbangkan kasus dari suatu strem map foo {1..} di mana foo adalah suatu fungsi atas integer/bilangan bulat. Kemudian, kapan saja kita mengakses suatu elemen strem ini, ungkapan/expression yang bersesuaian foo I akan dihitung kembali, yang mungkin mahal, tergantung pada fungsi foo yang ada.
Untuk menghindari pemborosan/ketidakcakapan seperti itu, bahasa Q menyediakan operator
‘&’ ( ampersand atau “ memoize”). Manakala berlaku untuk suatu argument khusus (seperti suatu anggota stream) [itu] menyebabkan nilai untuk di-memoized. Ini berarti bahwa nilai akan hanya dihitung pertama kali [itu] diperlukan. Masing-masing akses yang berikut kemudian akan kembalikan secara sederhana nilai yang telah dihitung.
Dalam contoh kita, kita dapat memoize kedua kepala/heads dan ekor/tails dari stream, dengan menerapkan operator ‘&’ (ampersand atau “memorize”) secara berulang bagi semua heads dan tails. Standard libraryberisi fungsi “lazy” yang mana baru sajamelakukan ini. Semua kita harus lakukan untuk menerapkan/memberlakukan fungsi ini kepada stream yang ada agar semua elemen di-memoized:
==> def foo = \X.writes $ "foo: "++str X++"\n" || X ==> def S = lazy (map foo {1..})
==> list (take 2 S) foo: 1 foo: 2 [1,2] ==> list (take 4 S) foo: 3 foo: 4 [1,2,3,4]
Catat bahwa ketika menghitung empat anggota stream yang pertama dengan daftar/list (take 4 S), hanya element nomor 3 dan 4 yang dihitung, karena dua element yang pertama telah dihitung dengan daftar/list (take 2 S) sebelumnya.
Alasan yang khusus bahwa argument tidak memoized secara otomatis terus menerus adalah bahwa beberapa argument boleh melibatkan fungsi dengan efek samping, dalam hal mana memoisasi oftenundesirable. Sebagai contoh, kita dapat menghitung liat/daftar lima nilai-nilai acak dengan senang hati menggunakan suatu pengertian list sebagai berikut:
==> [random : I in [1..5]]
Ini bekerja sebab subterm yang acak benar-benar suatu parameter khusus untul fungsi standard library listof yang menerapkan pengertian list (lihat Bagian 11.8 [Kondisi Bersyarat dan Pengertian (Conditionals and Comprehensions)], halaman 127), sehingga akan dihitung kembali sebagai masing-masing elemen list yang diproduksi. Bagaimanapun, jika kita memoize subterm yang acak kemudian fungsi akan hanya dievaluasi sekali ketika untuk memberi nilai bagi semua anggota list:
==> [&random : I in [1..5]]
[3194909515,3194909515,3194909515,3194909515,3194909515]