Haskel
Haskel
MAKALAH
Penyusun: Anjar Priandoyo Luqman Rakhman Sapta Ferdinansyah Hayuning Titi K. Y.B. Dafferianto T. Anshari Agro Rachmatullah
PROGRAM STUDI ILMU KOMPUTER FAKULTAS MATEMATIKA DAN ILMU PENGETAHUAN ALAM UNIVERSITAS GADJAH MADA 2004
KATA PENGANTAR
Pertama-tama kami mengucapkan syukur kepada Tuhan Yang Maha Esa, karena berkatNya lah akhirnya kami satu tim dapat menyelesaikan makalah mengenai bahasa pemrograman Haskell ini. Adapun pembuatan makalah ini ditujukan untuk penyelesaian tugas untuk mata kuliah Konsep Bahasa Pemrograman program studi Ilmu Komputer FMIPA UGM, selain juga sebagai pengantar kepada pembaca untuk lebih mengerti bahasa Haskell sebagai bahasa fungsional, juga untuk menambah khasanah keilmuan tentang bahasa pemrograman yang ada terlebih yang sangat berminat pada bidang pemrograman. Bahasa pemrograman merupakan implementasi dari algoritma yang dibuat oleh pemrogram dalam memberikan perintah kepada komputer untuk menyelesaikan masalah tertentu. Sampai saat ini banyak bahasa program yang dikembangkan seiring dengan kebutuhan akan kemudahan dalam pemrograman. Bahasa pemrograman dikategorikan dalam beberapa kategori berdasarkan kedekatannya dengan bahasa manusia, yaitu bahasa tingkat tinggi (high level language), bahasa tingkat menengah (middle level language), dan bahasa tingkat rendah (low level language). Semakin tinggi tingkatan sebuah bahasa, semakin dekat dengan basaha manusia (dalam hal ini bahasa Inggris). Dari sudut pandang yang berbeda, bahasa pemrograman dikategorikan menjadi bahasa deklaratif, bahasa imperatif, dan bahasa fungsional. Bahasa prosedural dan object-oriented termasuk bahasa imperatif. Haskell merupakan salah satu contoh bahasa pemrograman fungsional. Berbeda dengan bahasa pemrograman terstruktur, bahasa fungsional tidak dieksekusi berdasarkan urutan perintah-perintah, tetapi dengan mengevaluasi ekspresi-ekspresi. Mungkin, Haskell kurang begitu familiar di kalangan mahasiswa, maka dari itu dalam makalah ini kami akan menjabarkan bahasa Haskell secara umum bersama contohcontoh sintaksnya dengan harapan dapat mengenalkan bahasa pemrograman ini. Akhirnya tak ada gading yang tak retak, makalah ini masih penuh kekurangan dan kesalahan maka kami berharap para pembaca dapan memberikan saran dan petunjuk agar mskslsh ini menjadi lebih berisi, semoga makalah ini dapat bermanfaat dan berkenan di hati para pembaca.
DAFTAR ISI
KATA PENGANTAR DAFTAR ISI 2 3
BAB I PENDAHULUAN
I.1 What is Haskell?
Haskell merupakan bahasa pemrograman yang fungsional, malas dan murni. Ia disebut malas karena tidak mengevaluasi ekspresi-ekspresi yang digunakannya yang sebenarnya memang tidak diperlukan untuk menentukan jawaban bagi suatu masalah. Kebalikan dari malas adalah teliti yang merupakan strategi pengevaluasian bagi kebanyakan bahasa pemrograman (C, C++, Java, bahkan ML). Salah satu ciri dari bahasa yang teliti adalah setiap ekspresi diteliti apakah hasil dari perhitungan tersebut penting atau tidak.(Hal ini mungkin tidak mutlak benar seluruhnya karena adanya pengoptimalan compiler yang sering disebut denganeleminasi kode matiyang akan menghilangkan ekspresi yang tidak digunakan di program yang sering dilakukan oleh bahasa-bahasa tersebut). Haskell disebut murni karena bahasa ini tidak memperbolehkan adanya efek samping (Efek samping adalah sesuatu yang mempengaruhi bagian di program. Misalnya suatu fungsi yang mencetak sesuatu ke layar yang mempengaruhi nilai dari variabel global. Tentu saja, suatu bahasa pemrograman yang tanpa efek samping akan menjadi sangat tidak berguna; Haskell menggunakan sebuah system monads untuk mengisolasi semua komputasi kotor dari program dan menampilkannya dengan cara yang aman. Haskell disebut bahasa fungsional karena evaluasi dari programnya sama dengan mengevaluasi sebuah fungsi dalam bahasa matematika murni. Hal ini juga yang membedakannya dari bahasa standard (seperti C dan Java) yang mengevaluasi sederetan pernyataan secara urut (inilah pola dari bahasa terstruktur ) Yang termasuk dalam bahasa pemrograman fungsional antara lain Lisp, Scheme, Erlang, Clean, Mercury, ML, OCaml, SQL, XSL dan lain-lain. Di antara bahasa fungsional tersebut, Haskell merupakan bahasa yang ideal dalam banyak hal. Bahasa fungsional seperti juga bahasa logika seperti Prolog adalah deklaratif. Kebalikannya yaitu bahasa procedural dan bahasa yang berorientasi pada obyek adalah terstruktur. Haskell memiliki sintak yang lebih mudah untuk dipahami
daripada Lisp--bahasa turunan--(terutama bagi pemrogram yang pernah menggunakan bahasa yang menggunakan tanda baca yang sederhana/ringan seperti Python, TCL and REXX). Kebanyakan operatornya bersifat infix, tapi ada juga yang prefix. Pengaturan indentasi dan modulnya sangatlah familiar dan mungkin sangat menarik, misalnya tanda kurung bersarang yang terlalu dalam (seperti yang terlihat dalam Lisp) dihindari.
menawarkan beberapa penyederhanaan dan penghilangan lubang-lubang jebakan karena ketidakhati-hatian. Setelah Haskell 98 stabil, kelihatan jelas sekali bahwa banyak program memerlukan akses ke suatu set pustaka fungsi yang lebih banyak (yakni berhubungan dengan input/output dan interaksi ringan dengan sistem operasi). Jika program ini menjadi fleksibel, sekumpulan pustaka harus distandarkan juga. Sejumlah usaha dilakukan oleh komite untuk memperbaiki pustaka Haskell 98.
Program fungsional seringjali lebih mudah untuk dimengerti. Dalam kasus Quicksort, tidak terlalu diperlukan pengetahuan mengenai Haskell maupun quicksort. Hal tersebut tidak dapat terjadi pada program C, yang membutuhkan waktu untuk dimengerti, dan sangat mudah untuk melakukan kesalahn kecil yang mengakibatkan program tidak dapat dieksekusi. 3. Tidak ada tumpukan pada memori. Tidak ada kemungkinan memperlakukan integer sebagai pointer, atau dilanjutkan dengan pointer null.
4. Kode dapat digunakan kembali. Bahasa pemrograman fungsional menggunakan polymorphism, yang akan meningkatkan penggunaan kembali kode. 5. Pelekatan yang kuat. Bahasa fungsional non-strict memliki fitur kuat lainnya: hanya mengevaluasi program seperti yang diperlukan untuk menghasilkan jawaban, yang sering disebut dengan lazy evaluation. Struktur data dievaluasi secukupnya untuk menghasilkan jawaban, dan sebagian mungkin tidak dievaluasi sama sekali. Hal ini memungkinkan pelekatan untuk menyusun bersama program yang sudah ada. Hal ini memungkinkan penggunaan kembali program, atau potongan program lebih sering dari yang dapat dilakukan oleh pengaturan terstruktur. 6. Abstraksi yang kuat. Secara umum bahasa fungsional menawarkan cara-cara baru yang kuat untuk meng-enkapsulasi abstraksi. Abstraksi mengizinkan kita untuk menentukan objek yang pekerjaan internalnya tersembunyi. Abstraksi merupakan kunci untuk membangun program modular yang dapat dipelihara. Satu mekanisme abstraksi yang kuat yang terdapat dalam bahasa fungsional adalah higher-order function. 7. Manajemen memori yang terintegrasi. Kebanyakan program rumit perlu mengalokasikan memori dinamis dari tumpukan (heap). Setiap bahasa fungsional memudahkan pemrogram dari beban manajemen penyimpanan tersebut. Penyimpanan dialokasikan dan diinisialisaikan secara implisit, dan diselamatkan secara otomatis oleh kolektor sampah.
1. Meningkatkan produktifitas programmer (Ericsson banyak memanfaatkan hasil percobaan Haskell dalam software telephony) 2. Lebih singkat, lebih jelas dan kode-kodenya mudah dibaca 3. Errornya semakin sedikit dan reabilitynya lebih tinggi 4. Membuat jarak antara programmer dengan bahasa itu lebih pendek 5. Waktu untuk membuat program menjadi lebih singkat Banyak alasan untuk menggunakan Haskell diantaranya adalah kita bisa menulis kode bebas atau bugs-free code dalam waktu yang lebih singkat dibanding bahasa lainnya. Bahasa ini terus-menerus berkembang dan kian hari makin banyak kelebihannya. Selain itu, Haskell cocok untuk berbagai macam aplikasi. Sebagai contoh: Quicksort dalam Haskell:
qsort[] = [] qsort(x:xs)= qsort elts_lt_x ++ [x] ++ qsort elts_lt_greq_x where elts_lt_x = [y|y<-xs, y<x] elts_greq_x = [y|y<-xs, y>= x]
Quicksort dalam C:
qsort(a, lo, hl) int a[], hi, lo; { int h, l, p, t; if (lo<hi) { l=lo; h=hi; p=a[hi]; do while ((l<h) && (a[l]<=p)) l=l+1; while ((h>l) && (a[h]>=p)) h=h-1; if (l<h) { t=a[l]; a[l]=a[h]; a[h]=t; } } while (l<h); t=a[l]; a[l]=a[hi]; a[hi]=t; qsort(a, lo, l-1); qsort(a, l+1, hi); } }
Di samping itu yang paling penting adalah komunitas pengguna Haskell yang sangat membantu dalam memecahkan permasalahan seputar pemrograman menggunakan Haskell. Bahasa pemrograman ini secara konstan berkembang (bukan berarti bahasa pemrograman ini tidak stabil, tetapi banyak perluasan yang telah ditambahkan pada beberapa compiler yang sangat membantu pengguna) dan seringkali usul pengguna diperhatikan saat perluasan akan diimplementasikan.
Konsep Bahasa Pemrograman
Namun dalam Haskell sangalah berbeda variabel untuk jenis ini tidak ada sama sekali. Suatu nama bisa berupa nilai, tetapi sekali diberikan nilai, nama tersebut mewakili nilai itu di seluruh program. Tidak boleh ada pengubahan. Dalam Haskell, " variabel" adalah kurang lebih seperti variabel dalam persamaan matematika. Sebagai inspirasi, lihat contoh dalam persamaan matematika di bawah ini:
10x + 5y - 7z + 1 = 0 17x + 5y - 10z + 3 = 0 5x - 4y + 3z - 6 = 0
Terlihat bahwa ada beberapa nama yang tidak diketahui nilainya berapa, akan tetapi yang tidak diketahui tersebut tidak akan berubah samapai kita berhasil menemukan jawabannya. Meniadakan efek samping Dalam Haskell, perhitungan fungsi tidak bisa mempunyai efek samping di dalam program itu. Kebanyakan efek samping di dalam program terstruktur mungkin jenis variabel yang didefinisikan kembali dalam panel akhir ( baik variabel global, atau lokal, atau kamus, daftar, atau struktur penyimpanan lain), tetapi tiap-tiap Peristiwa I/O adalah juga merupakan efek samping. Disain baik mungkin memerlukan keluaran dan masukan itu hanya terjadi di dalam suatu kesatuan terbatas dinamai fungsi. Sedikit pemrograman tersusun ditujukan untuk membaca dan menulis ke STDIO, file, alat grafis, dll., disemua tempat dan dengan cara yang sukar untuk diperkirakan. pemrograman fungsional mengandung lingkungan skrip tingkat yang lebih tinggi. Tidak ada perulangan Corak menarik Haskell yang lain adalah meniadakan segala konstruksi perulangan. Tidak ada for dan tidak ada while. Tidak Ada GOTO atau branch atau jmp atau break. Orang akan berpikir bahwa mustahil untuk mengendalikan pekerjaan suatu program tanpa konstruksi (terstruktur) dasar seperti; tetapi membuang hal-hal ini benarbenar sungguh melegakan. Ketiadaan perulangan sungguh sama halnya perihal tidak ada efek samping. Ketika masuk dalam suatu perulangan maka tidak bisa mendefinisikan variabel-variabel dengan nilai yan berbeda, tak ada yang membedakannya. Kebutuhan untuk percabangan adalah digunakan unutk membedakan aktivitas program. Sedangkan pemrograman fungsional tidak memiliki aktivitas, hanya definisi-definisi fungsi sehingga tidak memerlukan percabangan.tetapi tetap ada kemungkinan untuk mensimulasikan hampir semua konstruksi perulangan umum, yang sering menggunakan kata kunci sama seperti di bahasa yang lain, dan dalam suatu gaya yang sepertinya serupa ke konstruksi terstruktur. Tidak ada program order Yang lain yang tidak dibutuhkan olah Haskell adalah program order. Satuan definisi yang membenahi suatu program dapat terjadi dalam order manapun dan apapun juga. Untuk contoh, ini mungkin nampak seperti suatu masalah: Kutipan program :
-- Program excerpt j = 1+i
Konsep Bahasa Pemrograman
i = 5 -- Hugs session after loading above program -- Main> i -- 5 :: Integer -- Main> j -- 6 :: Integer
Yang perlu dipahami dalam program seperti di atas adalah bahwa i dan j tidak diberikan nilai-nilai, tetapi diberikan dari apa yang proses berikan. Sesungguhnya, i dan j adalah fungsi, dan contoh di atas adalah definisi fungsi. Pada banyak bahasa pemprograman terstruktur, tidak boleh mendefinisikan suatu fungsi lebih dari satu kali (setidaknya dalam lingkup yang sama).
10
Operator dan Fungsi Precedence (fungsi yang didahulukan) Operator di dalam Haskell dibagi dalam berbagai tingkatan yang lebih tinggi. Kebanyakan hal ini sama halnya dengan apa yang kamu harapkan dari bahasa pemrograman yang lain. Perkalian harus didahulukan penambahan, dan seterusnya (maka " 2*3+4" adalah 10, bukan 14). Di dalam Haskell sebuah precedence (fungsi atau nama yang didahulukan) adalah mudah untuk melakukan kesalahan. Fungsi harus didahulukan daripada operator. Hasil daripada pernyataan tersebut adalah untuk ekspresi " f g 5" artinya " apply g ( dan 5) sebagai argumen ke f "bukan" apply hasil dari (g 5) ke f". Kesalahan semacam ini akan menghasilkan suatu pemberitahuan kesalahan oleh compiler, karena, sebagai contoh, f akan memerlukan suatu Integer sebagai suatu argumen bukannya memerlukan fungsi yang lain. Sebagai contoh kesalahan yang lebih fatal adalah kita bisa membikin suatu argument yang valid tetapi salah, adalah :
double res1 = res2 = res3 = res4 = res4 = n = n*2 double 5^2 -- 'res1' is 100, i.e. (5*2)^2 double (5^2) -- 'res2' is 50, i.e. (5^2)*2 double double 5 -- Causes a compile-time error double (double 5) -- 'res4 is 20, i.e. (5*2)*2 double (double 5) -- 'res4 is 20, i.e. (5*2)*2
Sama Seperti bahasa pemrograman yang lainnya, tanda kurung sangat bermanfaat di dalam ekspresi-ekspresi yang bisa bermakna ambigu, di mana kamu ragu tentang mana yang didahulukan (precedence). Tanda kurung tersebut tidak digunakan pada argumen fungsi di dalam Haskell, tetapi tidak juga salah bila ada, yang mana hanya akan menciptakan tambahan penggolongan ekspresi baru ( seperti pada contoh res2 di atas).
11
where x = 12 -- define 'x' within the guards z = 5 -- define 'z' within the guards n1 = x 1 2 -- 'n1' is 144 ('x' is the function name) n2 = x 33 1 -- 'n2' is 60 ('x' is the function name)
Argumen hanya mempunyai suatu scope atau lingkup di dalam definisi fungsi tertentu dan nama yang sama dapat digunakan di dalam definisi fungsi yang lain. Bagian yang dapat dicatat dari bahasa pemrograman Haskell ini adalah definisi fungsi tersebut di dalam Haskell akan menjadi fungsi yang lebih singkat dibanding bahasa pemrograman yang lain. Ini merupakan sebagian dari kaitan dengan sintaks Haskell yang ringkas, tetapi suatu alasan lebih kuat adalah karena penekanan di dalam fungsional programming adalah memerinci permasalahan ke dalam bagian komponen mereka ( dibanding hanya, "membuat apa yang perlu untuk dilaksanakan" pada masingmasing poin di dalam suatu pemrograman terstruktur ). Ini mendorong reusabilitas (penggunaan kembali) komponen, dan mengijinkan banyak verifikasi lebih baik yang mana masing-masing bagian benar-benar mengerjakan apa yang seharusnya dilakukan.. Bagian kecil dari definisi fungsi dapat dijelaskan dengan beberapa cara. Pertama adalah untuk menggambarkan banyak pendukungan terhadap fungsi di dalam suatu source-file, dan menggunakannya jika dibutuhkan. Bagaimanapun, ada dua cara yang ekivalen untuk mendefinisikan pendukungan Fungsi di dalam lingkup dari suatu definisi fungsi yang tunggal, yaitu the let Clause dan the where clause. salah satu contoh sederhan dari penjelasan diatas :
f n = n+n*n f2 n = let sq = n*n in n+sq f3 n = n+sq where sq = n*n where sq = n*n
Ketiga definisi diatas adalah ekivalen, tetapi f2 dan f3 memilih untuk menggambarkan fungsi sq di dalam lingkup definisi.
III.4 Importing/exporting
Haskell juga mendukung suatu sistem modul yang mempertimbangkan skala modularitas fungsi yang lebih besar. Dua elemen dasar dari modul control adalah spesifikasi import dan export suatu variable. Sebagai contoh :
-- declare the current module, and export only the objects listed module MyNumeric ( isPrime, factorial, primes, sumSquares ) where import MyStrings -- import everything MyStrings has to offer
12
-- import only listed functions from MyLists import MyLists ( quicksort, findMax, satisfice ) -- import everything in MyTrees EXCEPT normalize import MyTrees hiding ( normalize ) -- import MyTuples as qualified names, e.g. -- three = MyTuples.third (1,2,3,4,5,6) import qualified MyTuples
13
Integer ke Integer), juga tipe terstruktur [Integer] (list integer homogen) dan (Char,Integer) (pasangan integer dan karakter). Semua nilai dalam Haskell adalah first-class- dapat dilewatkan sebagai argumen ke fungsi, dikembalikan sebagai hasil, diletakkan dalam struktur data, dll. Tipe dalam Haskell, pada sisi lain, bukan merupakan first class. Tipe dalam hal mendeskripsikan nilai, dan asosiasi dari nilai dengan tipenya dinamakan typing. Typing dapat ditulis sebagai berikut:
5 a inc [1,2,3] (b,4) :: :: :: :: :: Integer Char Integer -> Integer [Integer] (Char,Integer)
:: dibaca bertipe. Fungsi dalam Haskell biasanya digambarkan sebagai rangkaian persamaan. Sebagai contoh fungsi inc dapat digambarkan sebagai persamaan tunggal:
inc n = n+1
Sebuah persamaan merupakan contoh dari sebuah deklarasi. Jenis lain dari deklarasi adalah type signature declaration, di mana dapat dideklarasikan typing secara eksplisit untuk inc:
inc :: Integer -> Integer
Untuk tujuan pendidikan, jika akan diindikasikan sebuah ekspresi c1 menghasilkan atau menurunkan ekspresi atau nilai c2, maka ditulis:
c1
c2
Sebagai contoh:
inc(inc 3)
Sistem tipe statis pada Haskell menggambarkan hubungan formal antara tipe dan nilai. Sistem tipe statis meyakinkan program Haskell type safe; yaitu pemrogram aman dari kesalahan pemilihan tipe. Sebagai contoh tidak dapat dilakukan penjumlahan 2 karakter, sehingga ekspresi a+b merupakan penulisan yang salah. Keuntungan utama dari bahasa bertipe statis adalah semua kesalahan/error tipe terdeteksi saat program dicompile. Tidak semua kesalahan ditangkap oleh sistem tipe; ekspresi seperti 1/0 dapat bertipe, tetapi akan menghasilkan kesalahan pada waktu eksekusi. Tetapi sistem tipe
14
masih menemukan kesalahan program pada waktu compile, yang mengizinkan compiler untuk menghasilkan kode yang lebih efisien. Sistem tipe juga memastikan bahwa user-supplied type signature adalah benar. Dalam kenyataannya sistem tipe pada Haskell cukup kuat untuk mengizinkan pengguna untuk menghindari menulis type signature sama sekali; dengan kata lain sistem tipe menyimpulkan tipe yang benar untuk pengguna.
a. List dari integer (seperti [1,2,3]), list dari karakter ([a,b,c]), sekalipun list dari list integer, dll, adalah anggota dari keluarga ini. (Sebagai catatan [2,b] bukan contoh yang valid karena tidak ada tipe tunggal yang berisi 2 dan b). List merupakan struktur data yang umum digunakan dalam bahasa fungsional, dan merupakan sarana yang baik untuk menerangkan prinsip-prinsip dari polimorfisme. List [1,2,3] dalam Haskell merupakan kependekan dari list 1:(2:(3:[])), di mana [] merupakan list kosong dan : merupakan operator infix yang meletakkan argumen pertama di depan argumen kedua (dalam hal ini list), atau dapat juga ditulis 1:2:3:[]. Sebagai contoh dari fungsi user-defined yang beroperasi pada list, pertimbangkan masalah menghitung jumlah elemen dalam list berikut:
length length [] length (x:xs) :: [a] -> Integer = 0 = 1 + length xs
Dari persamaan di atas terbaca: Panjang dari list kosong adalah 0, dan panjang dari list di mana elemen pertamanya x dan sisanya xs adalah 1 ditambah panjang dari xs. (sebagai catatan xs merupakan jamak dari x, dan harus dibaca demikian). Walaupun secara intuitif, contoh di atas menyorot sebuah aspek penting dari Haskell, yaitu pattern matching (pencocokan pola). Ruas kiri persamaan mengandung pola seperti [] dan x:xs. Dalam sebuah fungsi aplikasi, pola-pola seperti ini dibandingkan dengan parameter aktual (nyata) dengan cara intuitif ([] hanya cocok dengan list kosong, dan x:xs akan cocok dengan list apa saja dengan minimal satu elemen, x dihubungkan dengan elemen pertama dan xs dengan sisanya dalam list). Jika pencocokan sukses, ruas kanan dievaluasi dan dikembalikan sebagai hasil dari aplikasi. Jika gagal, persamaan berikutnya akan dicoba, dan jika semua persamaan gagal, maka hasilnya error.
15
Fungsi length di atas juga merupakan contoh dari sebuah fungsi polimorfis. Fungsi tersebut dapat diterapkan pada list yang berisi elemen bertipe apa saja, sebagai contoh [Integer], [Char], atau [[Integer]].
length [1,2,3] length [a,b,c] length [[1],[2],[3]]
3 3 3
Berikut adalah 2 contoh fungsi polimorfis dalam list. fungsi head mengembalikan elemen pertama dari list, fungsi tail mengembalikan semua elemen kecuali elemen pertama.
head :: [a] -> a head (x:xs) = x tail :: [a] -> [a] tail (x:xs) = xs
Tidak seperti fungsi length, fungsi-fungsi di atas tidak didefinisikan untuk semua nilai yang mungkin dari argumen mereka. Runtime error (kesalahan saat program dijalankan) akan muncul saat fungsi-fungsi tersebut diterapkan pada list kosong. Dengan tipe polimorfis beberapa tipe lebih umum dibanding lainnya dalam hal himpunan nilai yang didefinisikan lebih besar. Sebagai contoh tipe [a] adalah lebih umum dari [Char]. Dengan kata lain, tipe terakhir dapat diturunkan dari sebelumnya dengan substitusi yang cocok untuk a. Dibandingkan dengan bahasa bertipe monomorfis seperti C, polimorfisme meningkatkan ke-ekspresif-an, dan tipe inferensi meringankan beban tipe dari pemrogram. Tipe prinsipal (utama) dari ekspresi atau fungsi adalah tipe yang paling tidak umum, secara intuitif, berisi semua instan dari ekspresi. Sebagai contoh, tipe prinsipal dari fungsi head adalah [a] -> a; [b] -> a, a -> a.
Tipe yang didefinisikan di sini adalah Bool yang mempunyai dua nilai, yaitu True dan False. Tipe Bool adalah sebuah contoh dari tipe konstruktor, dan True dan False adalah data konstruktor (atau konstruktor saja).
16
Bool dan Color dalam contoh di atas merupakan tipe enumerasi. Berikut contoh dari tipe dengan hanya satu data konstruktor:
data Point a = Pt a a
Karena merupakan konstruktor tunggal, tipe seperti Point sering disebut tipe tuple, karena merupakan produk kartesian (dalam hal ini biner) dari tipe lain. Berlawanan dengan itu, tipe multi-konstruktor seperti Bool dan Color disebut union dari tipe sum. Point merupakan contoh dari tipe polimorfis: untuk semua tipe t, ia mendefinisikan tipe dari titik-titik kartesian yang menggunakan t sebagai tipe koordinat. Dalam contoh sebelumnya [] juga merupakan tipe konstruktor. Pada tipe t (bertipe apa saja) dapat diterapkan [] untuk menghasilkan tipe baru [t]. sintaks Haskell mengizinkan [] t ditulis sebagai [t]. -> juga merupakan tipe konstruktor; diberikan 2 tipe t dan u, t -> u merupakan tipe dari fungsi pemetaan elemen dari tipe t ke elemen dari tipe u. Catatlah bahwa tipe dari konstruktor data biner Pt adalah a -> a -> Point a, sehingga yang berikut adalah valid:
Pt 2.0 3.0 Pt a b Pt True False :: :: :: Point Float Point Char Point Bool
Pada sisi lain, ekspresi seperti Pt a 1 tidak valid karena a dan 1 berbeda tipe. Penting untuk membedakan antara menerapkan data konstruktor untuk menghasilkan nilai, dan menerapkan tipe konstruktor untuk menghasilkan tipe; yang pertama terjadi saat run-time dan bagaimana kita menghitung dalam Haskell, di mana yang terakhir terjadi saat compile-time. Tipe konstruktor seperti Point dan data konstruktor seperti Pt adalah dalam namespaces terpisah. Ini memungkinkan nama yang sama untuk digunakan oleh tipe konstruktor dan data konstruktor, seperti berikut:
data Point a = Point a a
17
Di sini didefinisikan tipe polimorfis pohon biner yang mana elemen-elemennya adalah node Leaf berisi nilai dari tipe a, atau node internal (branch) berisi dua sub-tree (rekursif). Saat membaca deklarasi data seperti ini, ingat bahwa Tree adalah tipe konstruktor, di mana Branch dan Leaf adalah data konstruktor. Di samping menciptakan koneksi antara konstruktor-konstruktor ini, deklarasi di atas mendefinisikan tipe berikut untuk Branch dan Leaf:
Branch Leaf :: :: Tree a -> Tree a -> Tree a a -> Tree a
Dengan contoh di atas telah didefinisikan suatu tipe yang cukup kaya untuk mendefinisikan beberapa fungsi (rekursif) yang menggunakannya. Sebagai contoh akan didefinisikan sebuah fungsi fringe yang mengembalikan sebuah list dari semua elemen dalam daun dari sebuah pohon dari kiri ke kanan. Biasanya akan sangat membantu jika dituliskan terlebih dulu tipe dari fungsi baru; dalam kasus ini tipe seharusnya Tree a -> [a]. fringe merupakan fungsi polimorfis di mana untuk semua tipe a, memetakan pohon dari a ke list dari a. Definisi yang tepat sebagai berikut:
fringe :: Tree a -> [a] fringe (Leaf x) = [x] fringe (Branch left right) = fringe left ++ fringe right
= =
Tipe sinonim tidak mendefinisikan tipe baru, tetapi memberi nama baru kepada tipe-tipe yang sudah ada. Sebagai contoh tipe Person -> Name setara dengan (String,Address) -> String. Nama yang baru seringkali lebih pendek dari tipe sinonimnya, tetapi itu bukan satu-satunya tujuan dari tipe sinonim: tipe sinonim
18
meningkatkan kemudahan membaca sebuah program dengan menjadi lebih mnemonik. Bahkan tipe polimorfis sekalipun dapat diberi nama baru:
type AssocList a b = [(a,b)]
Ini merupakan tipe asosiasi yang mengasosiasikan nilai dari tipe a dengan nilai dari tipe b.
IV.6 Operator
Untuk tipe data angka, terdapat operator aritmatika untuk penjumlahan, pengurangan, perkalian, dan pembagian. Dengan operator-operator tersebut, kita bisa menggunakan interpreter Haskell sebagaimana menggunakan kalkulator. Inilah beberapa contohnya:
? 1 + 1 > 2 ? 5 3 > 2 ? 7 * 7 > 49 ? 4 / 2 > 2.0 ? (2 * 2) - (4 * 1 * 3) > -8 ? 3.14 * 2.1 * 2.1 > 13.8474
Dalam contoh-contoh di atas, tanda tanya (?) mengawali input yang kita tuliskan dan tanda lebih besar (>) mengawali outputnya. Salah satu hal yang menarik adalah bahwa pembagian Integer dengan Integer menghasilkan bilangan floating point. Hal ini seperti di Pascal, dan berbeda dengan LISP (nenek moyang Haskell) dan C/C++/C#/Java. Di Haskell, list merupakan tipe data yang fundamental. Seperti yang telah disebutkan, list hanya bisa berisi satu tipe data (misalnya integer). Membuat list sangatlah mudah, yaitu dengan syntax berikut:
? [1, 2, 3, 4] > [1,2,3,4] ? [a, b, c] > [1,2,3,4] ? [3.14, 2.72] > [3.14,2.72] ? [] > []
19
Kita juga dapat membuat deret tak hingga misalnya [1,3,..], tetapi kalau itu kita lakukan maka interpreter tidak akan pernah selesesai menuliskan anggota deret tersebut (kecuali kalau kita hentikan). Salah satu operasi list yang sederhana adalah penjumlahan list, contohnya:
? [1..10] ++ [21..30] > [1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,27,28,29,30]
BAB V PERCABANGAN
V.1 Percabangan
Statement if-then-else terdapat di bahasa Haskell. Contoh penggunaannya adalah sebagai berikut:
> if 1 < 2 then Satu lebih kecil dua. else Satu tidak lebih kecil dua. ? "Satu lebih kecil dua.
Selain if-then-else, dalam Haskell juga terdapat statement case-of, sebagai contoh:
take m ys = case (m,ys) of (0,_) -> [] (_,[]) -> [] (n,x:xs) -> x : take (n-1) xs
20
BAB VI FUNGSI
VI.1 Fungsi
Fungsi dapat dibuat dengan menuliskan tipenya, lalu mendefinisikannya. Misalnya:
tambahSatu :: Integer -> Integer tambahSatu x = x + 1
berarti bahwa fungsi yang kita buat bernama tambahSatu, lalu dia menerima input sebuat Integer dan akan mengembalikan nilai Integer. Sebetulnya jika kita tidak menentukan tipe fungsi, interpreter akan menganalisa fungsinya untuk ditentukan tipenya. Walaupun begitu akan lebih jelas bagi pembaca (dan juga meringankan tuga interpreter) jika tipe fungsinya dituliskan oleh kita.
21
Kita bisa lihat bahwa definisi fungsinya sangat mirip dengan notasi matematika untuk fungsi yang sama, yaitu f(x) = x + 1. Catatan: Dalam interpreter Hugs 98, definisi fungsi harus berada di sebuah file teks dan untuk menggunakannya kita harus meload file yang bersangkutan dari Hugs 98. File harus diberi awalan yang menunjukkan nama modul. Sebagai contoh, untuk menggunakan fungsi tambahSatu kita harus memiliki file teks dengan isi sebagai berikut:
module Test where tambahSatu :: Integer -> Integer tambahSatu x = x + 1
Jika fungsi tersebut kita save sebagai Test.hs di folder d:\Haskell, maka untuk meloadnya dari Hugs 98 kita harus memasukkan perintah berikut:
? :load "D:\Haskell\Test.hs"
Jika filenya kita ubah lalu kita ingin meload ulang dari Hugs 98, maka kita cukup mengetikkan perintah berikut:
? :reload
Di dalam definisinya, fungsi tersebut menggunakan pattern matching dan rekursi. Pattern matching akan dikenakan pada argumen fungsi. Sebagai contoh, misalnya kita
22
memanggil faktorial 3. Pertama dicek apakah argumen match dengan 0. Karena tidak, maka dicek apakah argument match dengan n. Karena n bisa berupa Integer apapun (jenis fungsinya adalah Integer -> Integer), maka 3 match dengan n dan definisi fungsi yang berkaitan digunakan. Sebagai ilustrasi dari fleksibilitas pattern matching di Haskell, fungsi faktorial juga dapat didefinisikan sebagai berikut:
faktorial :: Integer -> Integer faktorial 0 = 1 faktorial (n + 1) = (n + 1) * faktorial (n)
Dalam kasus ini, 3 akan match dengan (n + 1), sebab 3 adalah (n + 1) dengan
n=2.
Perhatikan bahwa jika nilai argumen lebih besar atau sama dengan 0, maka argumennya akan dikembalikan, sedangkan jika tidak maka yang akan dikembalikan adalah negatif dari argumennya.
VI.3 List
Selanjutnya kita akan membahas penggunaan list sebagai argumen fungsi.
[a]. Untuk membatasi pada list Integer misalnya, digunakan [Integer].
Untuk
menyatakan bahwa fungsi kita dapat menerima/menghasilkan list apapun, digunakan Inilah definisi fungsi yang mengecek apakah sebuah list kosong beserta penggunaannya:
listKosong :: [a] -> Bool listKosong [] = True listKosong x = False ? listKosong [] > True ? listKosong [1..10]
23
Dari contoh di atas kita tahu bahwa list [10..1] ternyata adalah list kosong. Artinya konstruksi list [a..b] akan menghasilkan list tidak jika dan hanya jika a lebih kecil atau sama dengan b. Kita akan mendefinisikan fungsi yang mengembalikan hasil jumlahan semua anggota list.
jumlah :: [Integer] -> Integer jumlah [] = 0 jumlah (x:xs) = x + jumlah xs ? jumlah [1, 5, 4] > 10 ? jumlah [1..10] > 55 ? jumlah [] > 0
Kemunculan (x:xs) berarti sebuah list yang elemen awalnya x dan list sisanya xs. Sebagai contoh, list [1, 5, 4] akan match dengan pattern (x:xs) sebab [1, 5, 4] memiliki sebuah elemen awal yaitu 1 dan list sisanya adalah [5, 4]. Kita bisa lihat bahwa sisanya (xs) digunakan lagi sebagai argumen fungsi jumlah. Dengan apa yang telah dipelajari, kita sudah dapat membuat banyak fungsi:
balikList :: [a] -> [a] balikList [] = [] balikList (x:xs) = balikList xs ++ [x] ? balikList [1..10] > [10,9,8,7,6,5,4,3,2,1] adaDiList :: [Integer] -> Integer -> Bool adaDiList [] n = False adaDiList (x:xs) n = if x == n then True else (adaDiList xs n) ? adaDiList [1..10] 3 > True ? adaDiList [1..10] 11 > False hilangkan :: [Integer] -> Integer -> [Integer] hilangkan [] n = [] hilangkan (x:xs) n = if x == n then hilangkan xs n else
Konsep Bahasa Pemrograman
24
[x] ++ (hilangkan xs n) ? hilangkan [1, 2, 2, 1] 2 > [1,1] ? hilangkan [5, 4, 3, 2, 1] 3 > [5,4,2,1]
Di bawah akan diberikan contoh fungsi-fungsi yang menggunakan konstruksi bahasa yang lebih lanjut lagi. Dengan melihat contoh-contoh di bawah, anda dapat melihat bahwa Haskell adalah bahasa yang ekspresivitasnya sangat tinggi.
cartesian :: [Integer] -> [Integer] -> [[Integer]] cartesian x y = [[i,j] | i <- x, j <- y] ? cartesian [1..5] [8,9] > [[1,8],[1,9],[2,8],[2,9],[3,8],[3,9],[4,8],[4,9],[5,8],[5,9]] awal :: [a] -> a awal (x:xs) = x ? awal [2, 3, 4] > 2 sisa :: [a] -> [a] sisa (x:xs) = xs ? sisa [2, 3, 4] [3,4] quicksort [] quicksort (x:xs) = = ++ ++ [] quicksort [y | y <- xs, y<x ] [x] quicksort [y | y <- xs, y>=x]
? quicksort [3, 4, 2, 5, 1] [1, 2, 3, 4, 5] printFrom a b = if a < b then print a >> printFrom (a + 1) b else print b ? > > > > > > > > > > printFrom 1 10 1 2 3 4 5 6 7 8 9 10
25
contoh, sebuah fungsi ekuivalen dengan inc dapat ditulis sebagai \x -> x+1. Sama seperti fungsi add yang ekuivalen dengan \x -> \y -> x+y. Abstraksi lambda bersarang seperti ini dapat ditulis dengan notasi singkat yang ekuivalen \x y -> x+y. Dalam kenyataannya, persamaan :
inc x = x+1 add x y = x+y
Kita akan membahas lebih banyak mengenai ekuivalensi selanjutnya. Secara umum, diambil bahwa x memiliki jenis t1 dan exp memiliki tipe t2 maka \x->exp memiliki tipe
t1->t2.
Secara leksikal, operator infix terdiri seluruhnya dari simbol yang berlawanan dengan pengenal normal yang (2.4). Haskell tidak memiliki operator prefix, dengan pengecualian dari minus(-), yang merupakan infix dan prefix. Sebagai contoh yang lain, operator infix yang penting pada fungsi adalah untuk komposisi fungsi
(.) :: (b->c) -> (a->b) -> (a->c) f . g = \ x -> f (g x)
Sections Karena operator infix hanya merupakan fungsi, ini membuatnya bisa diterapkan sebagian dengan baik. Pada Haskell applikasi sebagian dari operator infix disebut section. Sebagai contoh:
(x+) _ \y -> x+y
26
Bentuk terakhir dari section diatas mengikat sebuah operator infix pada sebuah nilai fungsional yang ekuivalen. Dan ini adalah mudah saat melewatkan sebuah operator infix sebagai argumen daripada fungsi, Seperti pada map (+)
[1,2,3] (pembaca harus mengecek bahwa hal ini mengembalikan daftar dari
fungsi), Juga penting saat memberikan fungsi type signature. Sebagai contoh (++) dan (.) diberikan selanjutnya. Kita sekarang dapat melihat add yang didefinisikan sebelumnya hanyalah
(+), dan inc hanyalah (+1)! Sebenarnya definisi dapat berbentuk sesederhana
berikut:
inc = (+ 1) add = (+)
Kita dapat mengikat sebuah operator infix dalam nilai fungsional, Tetapi dapatkah kita melewati jalan yang lain? Ya, kita dapat menutup pengikat pengenal pada nilai fungsional pada backquotes. sebagai contoh x add y adalah sama seperti add x y1. Beberapa fungsi membaca lebih baik pada bentuk ini. Sebagai contoh daftar keanggotaan yang belum didefinisikan memperkirakan elem; ekspresi dari x elem xs dapat dibaca secara intuitif sebagai ]\x adalah elemen dari xs." Pada titik ini, pembaca mungkin dibingunkan dengan banyaknya cara untuk mendefinisikan fungsi. Keputusan untuk menyediakan mekanisme ini sebagian menyandarkan pada konsistensi (sebagai contoh, perlakuan pada infix vs fungsi reguler) Fixity Declarations Sebuah deklarasi tetap dapat diberikan untuk setiap operator infix atau constructor (meliputi hal lain yang dibuat dari pengenal umum, seperti elem). Deklarasi ini menetapkan tingkat precedence dari 0 sampai 9 (dengan 9 sebagai yang tertinggi; aplikasi normal diasumsikan memiliki tingkat precedence 10) dan kiri-kanan, atau non associativity. Sebagai contoh deklarasi tetap untuk for ++ dan . adalah:
infixr 5 ++ infixr 9 .
1
Perhatikan baik-baik bahwa add ditutup dengan tanda petik (backquotes) bukan apostrophes seperti digunakan pada sintak dari karakter; contohnya. 'f' adalah karakter, dimana `f` adalah operator infix. Untungnya, sebagian besat terminal ASCII membedakan cara ini daripada huruf yang digunakan pada manuskrip.
27
Keduanya menetapkan associativity kanan, yang pertaman dengan tingkat presedense dari 5 dan lainya 9. Associativity kiri ditetapkan melalui inifxl dan non associativity dengan infix. Juga fixity dari lebih dari satu operator dapat dispesifikasikan dengan deklarasi fixity yang sama. Jika tidak ada deklarasi tetap yang diberikan untuk sebagian operator, secara default diberikan infixl 9.
Dengan kata lain, bot adalah ekspresi non-terminating, Secara abstrak kita menunjukkan nilai dari ekspresi non-terminating sebagai (read bottom"). Ekspresi yang hasilnya dalam bentuk dari run-time errot, seperti 1/0, juga memiliki nilai ini. Beberapa error ini tidak dapat diperbaiki : Program tidak akan meneruskan kesalahan. Error ditangani dengan sistem I/O, seperti kesalahan pada end-of-file dapat diperbaiki dan ditangani dengan cara yang berbeda. (Seperti kesalahan I/O tidaklah benar-benar kesalahan seluruhnya tetapi lebih pada exception). Sebuah fungsi f dikatakan tegas jika, saat diaplikasikan pada ekspresi nonterminating juga gagal untuk melakukan terminasi. Dengan kata lain, f adalah tegas jika nilai dari f bot adalah . Pada kebanyakan bahasa pemrograman semua fungsi adalah tegas. Tetapi hal ini tidak berlaku pada haskell. Sebagai contoh yang sederhana, pandang const1, fungsi constant 1 didefinisikan dengan :
const1 x = 1
Nilai dari bot const1 bot pada Haskell adalah 1. Secara operasional, karena const1 tidak membutuhkan nilai dari argumennya, dia tidak berusaha untuk mengevaluasinya. Dan maka dari itu tidak pernah terperangkap pad komputasi nonterminating. Untuk alasan ini, fungsi tidak tegas (non-sctrict) disebut fungsi malas (lazy) dan dikatakan untuk mengevaluasi argumen lazy atau by need. Sejak kesalahan dan nilai nonterminating adalah secara semantik sama pada Haskell, argumen diatas juga menanggung kesalahan. Sebagai contoh, const1 (1/0) juga mengevaluasi pada 1. Fungsi non-strict secara ekstrim berguna dalam berbagai bidang. Manfaat utama adalah membebaskan programmer dari berbagai pertimbangan tentang perintah evaluasi.
28
Nilai hasil komputasi yang mahal, dapat dilewatkan sebagai argumen untuk fungsi tanpa perlu khawatir jika dikomputasikan jika mereka tidak dibutuhkan. Contoh penting dari ni adalah kemungkinan struktur data tak terbatas (infinite). Cara lain untuk menjelaskan fungsi non-strict adalah komputer Haskell menggunakan pendefinisian daripada penugasan yang ditemukan dalam bahasa tradisional. Membaca deklarasi seperti
v = 1/0
Seperti menenetapkan v sebagai 1/0 dari pada mengkomputasikan 1/0 dan menyimpan hasilnya pada v, Hanya jika nilai dari v dibutuhkan dengan kesalahan pembagian nol terjadi. Dengan sendirinya, deklarasi tidak termasuk komputasi manapun. Programming menggunakan assignment membutuhkan perhatian dengan baiki dalam rangka eksekusi. Definisi sebaliknya sangat sederhana. Mereka dapat dipresentasikan dengan berbagai cara tanpa mempengaruhi arti dari program.
ones = 1 : ones
29
Demikian numsFrom n adalah list infinite dari integer yang berturut-turut dimulai dari n. Dari sisni kita dapat membangun sebuah list infinite dari square.
squares = map (^2) (numsfrom 0)
Sebagai contoh, pindahkan elemen pertama dari list : ambil 5 squares => [0,1,4,9,16] Definisi diatas adalah contoh dari list sirkular. Pada banyak kemalasan (laziness) memilliki dampak penting pada efisiensi. Karena implementasinya dapat diharapkan untuk mengimplementasikan list sebagi struktur sirkular sebenarnya. Demikian dapat menghemat tempat. Untuk contoh lain penggunaan dari circularity, bilangan Fibonaci dapat dikomputasikan secara efisien, seperti pada sequence infinite berikut :
fib = 1 : 1 : [ a+b | (a,b) <- zip fib (tail fib) ]
Dimana zip adalah fungsi standar prelude yang mengembalikan pairwise interleaving dari argumen dua list.
zip (x:xs) (y:ys) = (x,y) : zip xs ys zip xs ys = []
Lihat bagaimana fib, sebuah list infinite, didefinisikan dalam istilahnya sendiri. seperti jika kita mengejar ekor, sebenarnya kita dapat menggambar dari komputasi seperti pada gambar diatas
30
kita ingin menterminasi program saat sesuatu telah menjadi salah. Sebagai contoh, definisi aktual dari head yang diambil dari prelude standar adalah:
head (x:xs) = x head [] = error "head{PreludeList}: head []"
31
32
4. Haskell cenderung sulit untuk didebug. Jadi, dengan semua kelebihan dan kekurangannya, apakah anda tertarik untuk membuat program dalam bahasa Haskell?
DAFTAR PUSTAKA
http://www.mta.ca/~rrosebru/oldcourse/371199/haskell http://haskell.org http://haskell.org/ghc http://haskell.org/hugs http://www.isi.edu/~hdaume/htut/ haskell.pdf haskell98-tutorial.pdf
33