Haskell: Bahasa Prosedural yang Unggul
(entropicthoughts.com)Memperlakukan efek samping sebagai nilai kelas satu
- Di Haskell, efek samping (misalnya pembuatan angka acak, output, dan sebagainya) diperlakukan seperti
first class value - Artinya, pemanggilan fungsi yang menghasilkan efek samping seperti
randomRIO(1, 6)bukan langsung nilai hasilnya, melainkan mengembalikan “objek yang mendeskripsikan aksi yang akan dijalankan nanti” - Objek ini akan menghasilkan nilai acak saat benar-benar dijalankan, tetapi sebelum itu hanya berisi rencana eksekusi
- Tipe seperti
IO Intmerepresentasikan “aksi yang akan menghasilkan Int saat benar-benar dijalankan”, dan tidak langsung dieksekusi pada saat dipanggil, melainkan saat nanti memang diperlukan - Karena sifat ini, berbeda dari bahasa prosedural tradisional yang menganggap “pemanggilan fungsi = langsung dieksekusi”, di Haskell efek samping bisa dikombinasikan lalu dijalankan belakangan
Menjelaskan do block tanpa mistik
- Blok
dobukan sintaks ajaib, tetapi pada dasarnya tersusun dari dua operator: menghubungkan (bind) efek samping dan menjalankannya secara berurutan (then)
then
- Operator
*>menjalankan efek samping di kiri, membuang nilai hasilnya, lalu melanjutkan dengan efek samping di kanan - Misalnya
putStr "hello" *> putStrLn "world"membuat satu aksiIO ()yang menggabungkan dua output secara berurutan - Saat menulis beberapa baris di dalam blok
do, secara internal digunakan operasi eksekusi berurutan seperti ini
bind
- Operator
>>=berfungsi menjalankan efek samping di kiri lalu meneruskan nilai yang dihasilkan ke fungsi di kanan - Contoh:
randomRIO(1, 6) >>= print_sidemembuat efek samping yang meneruskan hasil lemparan dadu keprint_sideuntuk dicetak - Pola
<-di dalam blokdoadalah cara ringkas untuk mengekspresikan operator ini
Dua operator adalah seluruh isi do block
- Pada akhirnya, blok
dodibangun dari dua operator ini:*>dan>>= - Sintaks
dosering dipakai karena keterbacaan dan kemudahannya, tetapi kelebihan Haskell lebih terasa jika kita memanfaatkan fungsi-fungsi komposisi efek samping yang lebih kaya dari itu
Fungsi yang beroperasi pada efek samping
- Ada berbagai fungsi di pustaka standar untuk menangani efek samping dengan cara yang lebih beragam
pure
pure xmembuat “aksi yang menghasilkan nilai x tanpa efek samping tambahan apa pun”- Contoh:
loaded_die = pure 4membuatIO Intyang selalu mengembalikan 4
fmap
- Dengan bentuk
fmap :: (a -> b) -> IO a -> IO b, fungsi ini membuat aksi baru yang menerapkan fungsi murni ke nilai hasil dari suatu efek samping - Contoh: seperti
length <$> getEnv "HOME", kita bisa membuat aksi yang mengambil variabel lingkungan lalu menerapkanlengthuntuk menghitung panjangnya
liftA2, liftA3, …
- Fungsi seperti
liftA2danliftA3menggabungkan hasil dari beberapa efek samping dengan satu fungsi murni untuk membuat efek samping baru - Contoh:
liftA2 (+) (randomRIO(1,6)) (randomRIO(1,6))membuat efek samping yang menjumlahkan dua nilai lemparan dadu - Pekerjaan yang sama juga bisa dilakukan dengan kombinasi
<$>dan<*>
Intermission: apa gunanya?
- Cara ini mungkin terlihat seperti kemampuan sederhana yang juga bisa dilakukan di bahasa lain, tetapi di Haskell ada kelebihan bahwa aksi efek samping bisa kapan saja dipisahkan ke variabel atau dikombinasikan ulang tanpa mengubah waktu eksekusi atau hasilnya
- Dengan menangani efek samping secara terpisah, refactoring kode jadi lebih minim kebingungan, dan reuse yang aman berbasis
equational reasoningmenjadi mungkin
sequenceA
sequenceA [IO a] -> IO [a]mengubah “daftar aksi efek samping” menjadi “satu aksi efek samping tunggal yang menghasilkan daftar hasil”- Contoh: kita bisa mengumpulkan beberapa aksi
logdalam sebuah daftar, lalu menjalankannya sekaligus nanti dengansequenceA - Bahkan efek samping yang berulang tanpa batas (misalnya
repeat (randomRIO(1,6))) bisa disimpan sebagai daftar, lalu hanyatake nseperlunya dan dijalankan dengansequenceA
Interlude: fungsi-fungsi praktis
void,sequenceA_,replicateM,replicateM_, dan lainnya berguna saat nilai hasil tidak dipakai atau saat ingin menjalankan aksi berulang kali- Contoh:
replicateM_ 500 (putStrLn "I will not cheat again.")memungkinkan kita menjalankan efek samping berkali-kali tanpa menghitung sendiri jumlah pengulangannya
traverse
traverse :: (a -> IO b) -> [a] -> IO [b]membuat aksi yang menerapkan fungsi berefek samping ke setiap elemen daftar lalu mengumpulkan hasilnya sebagai daftarsequenceAsebenarnya sama dengantraverse id, dantraverse_adalah versi yang membuang hasilnya
for
-
forpunya fungsi yang sama dengantraverse, tetapi menerima argumen dalam urutan terbalik -
Contoh: bentuk
for numbers $ \n -> ...memungkinkan ekspresi yang terasa alami seperti sintaks “for loop” -
Berkat komposisi seperti ini, pengulangan, traversal, dan transformasi struktur data yang di bahasa lain sering memerlukan sintaks khusus bisa diimplementasikan di Haskell dengan kombinasi fungsi pustaka
Memanfaatkan sifat kelas satu dari efek
- Jika efek samping dimanfaatkan secara aktif sebagai nilai kelas satu di Haskell, kita bisa mengurangi duplikasi kode dan memperbaiki struktur program
- Misalnya, dalam logika faktorisasi prima bilangan besar dengan cache, kita bisa memakai
Statealih-alihIOuntuk membentuk struktur yang “punya efek samping tetapi tidak memengaruhi dunia luar” - Efek samping yang distrukturkan seperti ini hanya diterapkan pada bagian yang perlu, sementara kode lainnya bisa tetap sebagai fungsi murni, sehingga keamanan dan fleksibilitas sama-sama terjaga
- Pada akhirnya, dengan
evalStatedan sejenisnya, efek tersebut bisa dijalankan lalu hasilnya diubah menjadi nilai murni
Hal-hal yang tidak perlu Anda pedulikan
- Berbagai nama lama dari era Haskell terdahulu (
>>,return,mapM, dan sebagainya) kini bisa digantikan oleh fungsi modern (*>,pure,traverse, dan sebagainya) - Nama-nama itu berasal dari “nama lama atau desain yang berpusat pada monad”, sedangkan sekarang pendekatan berbasis Applicative atau Functor yang lebih umum lebih dianjurkan
Lampiran A: Menghindari kesuksesan dan ketidakbergunaan
- Ungkapan “Haskell avoids success” berarti “bahasa ini tidak mengorbankan nilai-nilai dasarnya demi popularitas atau kemudahan semata”
- “Haskell is useless” merujuk pada konteks bahwa pada awalnya bahasa ini hanya mengizinkan fungsi murni sepenuhnya sehingga tampak seperti bahasa yang benar-benar tidak bisa melakukan apa pun, tetapi kemudian memperoleh kepraktisan lewat cara memperlakukan efek samping sebagai sesuatu yang ‘kelas satu’
Lampiran B: Mengapa fmap memetakan baik efek samping maupun daftar
fmapmemiliki bentuk yang sangat umum (Functor f => (a -> b) -> f a -> f b), sehingga bisa diterapkan secara seragam ke berbagai container atau tipe berefek seperti list, Maybe, dan IO- Jika
fmapditerapkan ke list, ia menerapkan fungsi ke semua elemen; jika diterapkan keIO, ia menerapkan fungsi ke nilai hasilnya - Dengan demikian, seluruh “struktur yang bisa dikenai penerapan fungsi” disebut Functor
Lampiran C: Foldable dan Traversable
Foldableadalah struktur yang elemennya bisa ditelusuri dan diprosesTraversableadalah struktur yang bukan hanya bisa ditelusuri, tetapi juga bisa dibangun ulang dengan bentuk yang sama menggunakan elemen baru- Agar
sequenceAatautraversebisa mengumpulkan nilai sambil mempertahankan struktur asal, struktur tersebut harus bersifatTraversable - Struktur data seperti tree atau Set bisa memiliki bentuk yang berubah tergantung nilainya, sehingga dibedakan antara kasus yang hanya bisa ditelusuri (
Foldable) dan kasus yang benar-benar bisa dibangun ulang (Traversable) - Sesuai kebutuhan, efek samping juga bisa ditangani secara fleksibel lewat cara seperti mengubahnya dulu menjadi list lalu memakai
traverse
2 komentar
Kalau lihat-lihat Reddit, iklannya sering banget muncul.. Tapi dari namanya saja sudah terasa ada semacam hambatan psikologis.
Entah kenapa kesannya seperti bahasa yang sangat sulit dan kuat..
Opini Hacker News
Sistem tipe Haskell memang rumit jika dibandingkan dengan bahasa populer lain. Khususnya, operator seperti
*>,<*>, dan<*meningkatkan kurva belajar di seluruh codebase>>=dan>>agar produktivitas tetap terjagaHaskell membantu memperbaiki pemrograman imperatif
Versi generalisasi dari
traverse/mapMsangat berguna karena bekerja bukan hanya untuk list tetapi untuk semua tipeTraversabletraverse :: Applicative f => (a -> f b) -> t a -> f (t b)Haskell memiliki monad yang kuat, dan ini membuat Haskell terasa lebih prosedural
doSalah satu perangkat lunak yang ditulis dengan Haskell adalah ImplicitCAD
Kode Haskell bisa dibaca seperti bahasa prosedural, tetapi tetap memberi keunggulan saat bekerja dengan fungsi berefek samping
>>adalah nama lama untuk<i>>, dan keduanya adalah operator asosiatif kiri>>didefinisikan sebagaiinfixl 1dan<i>>didefinisikan sebagaiinfixl 4, sehingga<i>>terikat lebih kuat daripada>>IO adanadi Haskell bisa terasa mirip dengan asinkron dan sinkronDi bahasa lain, IO sederhana bisa dilakukan dengan fungsi seperti
console.log("abc")Orang yang belum pernah mencoba Haskell bisa merasa bahwa Haskell nyata yang memakai ekstensi GHC terlalu rumit