3 poin oleh GN⁺ 2025-05-25 | Belum ada komentar. | Bagikan ke WhatsApp
  • Efek aljabar adalah fitur bahasa yang menangkap dan menangani alur kontrol seperti pengecualian yang dapat dilanjutkan kembali, merupakan fitur inti Ante, dan juga digunakan secara sentral di bahasa riset seperti Koka, Effekt, Eff, dan Flix
  • Dengan mekanisme yang sama, generator, pengecualian, async, coroutine, diferensiasi otomatis dapat dibuat di tingkat pustaka, dan berkat polimorfisme efek, fungsi seperti map cukup ditulis sekali tanpa bergantung pada jenis efeknya
  • Jika injeksi dependensi dan penyampaian konteks seperti akses database, output, logging, dan penerusan status diubah menjadi efek, mock untuk pengujian, pengumpulan output, dan pemfilteran log dapat ditangani dengan mengganti handler
  • Jika efek seperti can IO, can Print, can Fail terlihat di signature fungsi, hal itu menguntungkan untuk jaminan kemurnian, pencatatan/pemutaran ulang, dan audit keamanan, tetapi efek yang sudah diizinkan dapat tanpa sengaja merambat ke handler yang sudah ada
  • Kelemahan tradisionalnya adalah kekhawatiran soal efisiensi, tetapi bahasa-bahasa terbaru mengurangi biayanya lewat optimisasi efek tail-resumptive, evidence passing, pembatasan resume tunggal, dan spesialisasi handler

Model dasar efek aljabar

  • Efek aljabar juga disebut effect handlers, dan dapat dipahami dengan model “pengecualian yang dapat dilanjutkan kembali”
  • Dalam pseudocode Ante, fungsi efek dideklarasikan, dan signature fungsi menandai dengan can bahwa efek tersebut dapat digunakan
    • Memanggil fungsi efek seperti say_message: Unit -> Unit menjadi bentuk “melempar” efek
    • Fungsi pemanggil menampilkan kemungkinan penggunaan efek itu di signature, seperti foo () can SayMessage
  • Ekspresi handle menangkap efek mirip try/catch, lalu melanjutkan komputasi yang terhenti dengan memanggil resume
    • Jika handler say_message menjalankan print "Hello World!" lalu memanggil resume (), komputasi aslinya berlanjut dan mengembalikan 42
  • Nama “algebraic” sebagian besar adalah istilah yang tersisa; dalam praktiknya, effect handlers lebih akurat sebagai istilah, tetapi nama efek aljabar tetap dipakai karena lebih akrab bagi pengguna

Alur kontrol yang ditentukan pengguna

  • Efek aljabar memungkinkan berbagai fitur bahasa diimplementasikan dengan satu mekanisme
  • Polimorfisme efek mengurangi masalah what color is your function
    • map (input: Vec a) (f: a -> b can e): Vec b can e menyatakan bahwa apa pun efek e yang dilakukan fungsi masukan f, map juga melakukan efek yang sama
    • map yang sama dapat digunakan bersama output stdout, pemanggilan fungsi asinkron, yield stream, dan lain-lain
    • Banyak bahasa effect handler menghilangkan variabel efek e sehingga dapat ditulis dalam bentuk yang familier seperti map (input: Vec a) (f: a -> b): Vec b
  • Pengecualian dapat diimplementasikan saat menangani efek dengan tidak memanggil resume
    • Definisikan throw: a -> never_returns untuk efek Throw a
    • Jika terjadi pembagian dengan 0, panggil throw "error: Division by zero!", dan handler mencetak pesan lalu tidak melanjutkan komputasi
  • Generator dapat diimplementasikan dengan yield: a -> Unit dari efek Yield a
    • Saat menelusuri elemen vektor, panggil yield elem
    • Handler filter memanggil lagi yield x jika nilai yang di-yield memenuhi kondisi, lalu melanjutkan ke elemen berikutnya dengan resume ()
    • Handler my_for_each menjalankan fungsi f untuk setiap nilai yang di-yield lalu terus berjalan dengan resume ()
  • Scheduler kooperatif juga dapat dibuat dengan efek yield: Unit -> Unit, di mana handler mengambil alih kontrol lalu beralih ke eksekusi fungsi lain
  • Banyak efek dapat saling terkomposisi dengan baik, dan ini dianggap sebagai keunggulan kegunaan dibanding abstraksi efek lainnya

Injeksi dependensi dan kemudahan pengujian

  • Efek juga dapat digunakan untuk injeksi dependensi dalam aplikasi bisnis umum
  • Alih-alih langsung mengoper objek database sebagai argumen fungsi, efek Database dapat didefinisikan
    • Bentuk lama menerima objek DB sebagai argumen seperti business_logic (db: Database) (x: I32)
    • Bentuk berbasis efek menjadi business_logic (x: I32) can Database, dan di dalamnya memanggil query "..."
  • Pemilihan database konkret ditangani oleh handler di bagian atas stack pemanggilan
    • DB produksi bisa diganti ke DB lain atau ke mock DB untuk pengujian
    • Handler mock_database dapat mengabaikan pesan query dan selalu resume dengan mengembalikan DbResponse.Ok
  • Output juga bisa diperlakukan sebagai efek agar selama pengujian tidak menulis langsung ke stdout, melainkan dikumpulkan sebagai string
    • Handler print_to_string menangkap pemanggilan print msg dan mengakumulasikannya ke string all_messages bersama karakter baris baru
    • output_messages dapat memverifikasi nilai balik 1234 dan string pesan tanpa output nyata
  • Logging dapat diubah menjadi output bersyarat dengan efek Log dan LogLevel
    • log_handler memanggil print msg jika level pesan setidaknya sama dengan ambang yang ditetapkan
    • foo () with log_handler Error hanya mencetak log error

API yang lebih rapi dan penerusan konteks

  • Efek aljabar dapat mengekspresikan pola objek Context yang diteruskan ke seluruh program atau pustaka sebagai efek
  • Efek Use a dapat dipandang sebagai efek state, dengan get: Unit -> a dan set: a -> Unit
    • Handler state menyimpan state awal, mengembalikan konteks saat get, dan memperbaruinya ke konteks baru saat set
    • Definisi state pada contoh mengabaikan aturan ownership, dan implementasi nyata mungkin memerlukan batasan Copy a
  • Contoh yang menyimpan string di dalam vektor dan meneruskan indeks sebagai semacam kunci menunjukkan biaya penerusan konteks
    • Tanpa efek, push_string, get_string, append_with_separator, example, dan lainnya harus terus menerima strings sebagai argumen
    • Dalam implementasi berbasis efek, operasi primitif push_string dan get_string memanggil get/set, dan kode tingkat atas tidak perlu lagi meneruskan strings secara langsung
  • Pendekatan ini cocok ketika pustaka membungkus penerusan konteks internal
    • Pengguna pustaka tidak perlu memikirkan detail internal cara konteks diteruskan
    • Jika tidak ingin terikat ke tipe konteks tertentu, fungsi-fungsi yang dibutuhkan dapat diabstraksikan sebagai interface

Pengganti variabel global dan gaya langsung

  • API yang tampak stateless seperti pembuatan bilangan acak atau alokasi memori, tetapi sebenarnya memerlukan state, dapat diekspresikan sebagai efek alih-alih variabel global
  • Contoh pembuatan bilangan acak menunjukkan beban harus meneruskan objek Prng secara langsung ke seluruh program
    • Prng global memang nyaman, tetapi memunculkan kelemahan nilai global seperti kebutuhan thread safety
    • Dengan random: Unit -> U8 dari efek Random, pengguna hanya perlu menyatakan inisialisasi melalui handler di suatu titik pada stack pemanggilan atas
    • Setelah itu, untuk mengganti ke /dev/urandom atau sumber acak lain, cukup ganti handler tanpa mengubah sisa kode pada stack pemanggilan
  • Alokasi memori juga dapat diekspresikan sebagai efek Allocate
    • allocate: (size: Usz) -> Alignment -> Ptr a
    • free: Ptr a -> Unit
    • Sebagian besar pemanggilan dapat memakai allocator global, lalu di tight loop, handler dapat ditambahkan ke body loop untuk menggantinya dengan arena allocator
  • Efek memungkinkan gaya langsung dibanding cara yang meneruskan hasil dalam nilai yang dibungkus khusus
    • Saat memakai Maybe t, jalur sukses harus dirangkai dengan and_then, map
    • Gula sintaks seperti ? di Rust adalah perangkat untuk berfokus pada jalur yang baik
    • get_line_from_stdin (): String can Fail, IO dan parse (s: String): U32 can Fail berbasis efek dapat ditulis seperti kode sekuensial biasa: line = ..., x = ..., x * 2
  • Penanganan kegagalan dapat dilakukan dengan menerapkan handler agar keluar dari jalur baik
    • get_line_from_stdin () with default "42" menangani efek Fail dengan nilai default
  • Tipe error yang berbeda juga terkomposisi secara alami sebagai daftar efek
    • LibraryA.foo (): U32 can Throw LibraryA.Error
    • LibraryB.bar (): U32 can Throw LibraryB.Error
    • my_function dapat mendeklarasikan Throw LibraryA.Error, Throw LibraryB.Error, dan Throw MyError bersama-sama
    • Jika pengulangannya menjadi panjang, type alias seperti AllErrors = can Throw ... dapat dibuat
    • Efek Throw String yang sama akan digabung jadi satu, dan jika ingin dipisahkan maka diperlukan tipe pembungkus seperti MyError

Kemurnian, dapat dijalankan ulang, dan audit keamanan

  • Sebagian besar bahasa effect handler, kecuali yang setara dengan OCaml, menggunakan efek di tempat yang dapat menimbulkan side effect
    • Di Ante, side effect tidak dapat digunakan kecuali ditandai seperti can Print, can IO
    • Definisi extern tidak dapat diperiksa kompilator sehingga definisi tipenya harus dipercaya
    • Menjalankan efek IO hanya pada mode debug untuk mempertahankan keamanan efek pada mode rilis adalah fitur yang direncanakan
  • Beberapa fungsi memerlukan fungsi murni sebagai input
    • Saat membuat thread, thread yang dibuat tidak boleh dapat memanggil lewat handler yang dimiliki thread saat ini
    • spawn_all (functions: Vec (Unit -> a pure)): Vec a can IO menerima hanya fungsi murni, menjalankan semuanya sebagai thread, lalu menunggu hingga selesai
  • Software Transactional Memory (STM) adalah teknik konkurensi yang memerlukan fungsi murni
    • Banyak fungsi dijalankan secara bersamaan, lalu jika nilai berubah oleh thread lain selama transaksi, transaksi itu dimulai ulang
    • Implementasi proof-of-concept di Effekt tersedia di effekt-stm
  • Kemurnian juga dapat memberi kemungkinan record/replay mirip utilitas debugging rr
    • Dua handler, record dan replay, menangani efek tingkat atas yang dikeluarkan main, biasanya IO
    • record mencatat terjadinya efek dan hasilnya, lalu meneruskannya lagi ke handler IO bawaan untuk pemrosesan nyata
    • replay tidak melakukan IO nyata dan memakai hasil dari log efek
    • Jika pencatatan dijadikan default pada build debug, debugging deterministik dapat diperoleh
  • Daftar efek pada signature fungsi membantu audit keamanan, mirip Capability Based Security
    • get_pi: Unit -> F64 menunjukkan bahwa fungsi itu tidak diam-diam melakukan IO di belakang layar
    • Jika setelah pembaruan pustaka get_pi: Unit -> F64 can IO, maka akan muncul error pada kode pemanggil kecuali fungsi pemanggil itu memang sudah memerlukan IO
    • Sebaiknya mendeklarasikan efek seminimal mungkin, misalnya hanya Print alih-alih seluruh IO
    • Menambahkan efek baru diperlakukan sebagai perubahan yang melanggar semantic versioning
    • Materi terkait mencakup Capability Based Security dan Designing with Static Capabilities and Effects

Batasan dan strategi implementasi

  • Salah satu batasan pendekatan efek adalah kemungkinan penanganan yang tidak disengaja
    • Jika sebuah fungsi baru mulai memerlukan IO, mungkin tidak muncul error jika fungsi pemanggil sudah mengizinkan IO
    • Efek Fail juga demikian; fungsi pustaka yang sebelumnya tidak pernah gagal bisa saja nanti dapat Fail, lalu merambat ke handler Fail yang sudah ada
    • Perilaku ini kadang dapat diterima, tetapi jika yang diinginkan adalah penanganan terpisah seperti pemberian nilai default, hasilnya bisa berbeda dari niat semula
  • Kekurangan utama tradisional adalah kekhawatiran soal efisiensi, tetapi keluaran kompilasi efek belakangan ini jauh membaik
  • Banyak bahasa efek aljabar mengoptimalkan efek tail-resumptive menjadi pemanggilan closure biasa
    • Efek tail-resumptive adalah efek di mana handler memanggil resume di bagian akhir
    • Sebagian besar efek nyata termasuk kategori ini, dan sebagian besar contoh di artikel ini juga demikian
    • Pengecualian diklasifikasikan sebagai kasus istimewa karena sama sekali tidak memanggil resume
  • Strategi optimisasi juga berbeda menurut bahasa
    • Koka memakai evidence passing dan mengangkat efek hingga ke handler untuk mengompilasi ke C tanpa runtime
    • Ante dan OCaml membatasi resume agar dipanggil paling banyak satu kali
      • Batasan ini mengecualikan beberapa efek seperti nondeterminisme
      • Sebagai gantinya, penanganan resource menjadi lebih sederhana dan continuation internal bisa diimplementasikan lebih efisien dengan cara seperti segmented stacks
    • Effekt sepenuhnya menspesialisasi handler lalu menghapusnya dari program
      • Pendekatan ini punya batasan yang membuat sebagian besar fungsi menjadi second-class
      • Fungsi first-class dapat diperoleh dalam bentuk boxed dan dialihkan dengan model pay-as-you-go
      • Materi terkait mencakup dokumentasi captures Effekt dan makalah

Belum ada komentar.

Belum ada komentar.