4 poin oleh GN⁺ 2023-08-20 | 2 komentar | Bagikan ke WhatsApp
  • Railway Oriented Programming adalah pendekatan untuk menangani jalur kegagalan yang berulang dalam aplikasi nyata—seperti validasi, logging, dan error jaringan—di dalam pipeline fungsional
  • Intinya adalah menjelaskan keterbatasan contoh yang hanya menyambung jalur sukses dengan analogi railway, sehingga pengguna F# pun lebih mudah memahami alur error secara visual
  • Dari sudut pandang Haskell, ini dekat dengan tipe Either dan **komposisi Kleisli`, tetapi fokusnya adalah resep penanganan error dan pola implementasi, bukan teori abstrak
  • Untuk penerapan nyata, disediakan pustaka F# untuk NuGet Chessie, contoh GitHub yang membandingkan C# dan F#, serta contoh penerapan pada FizzBuzz
  • Ini adalah pendekatan yang berguna, tetapi tidak boleh diterapkan secara berlebihan ke semua masalah; karena F# tidak memiliki type class, pustaka contohnya mendefinisikan sendiri fungsi-fungsi yang dibutuhkan

Materi presentasi dan kode contoh

  • Halaman Railway Oriented Programming adalah kumpulan slide dan kode presentasi di satu tempat
  • Titik berangkatnya adalah masalah bahwa hanya dengan “happy path” yang sering diasumsikan oleh contoh pemrograman fungsional, sulit membangun aplikasi nyata
  • Aplikasi yang tangguh harus menangani validasi, logging, error jaringan, error layanan, dan berbagai situasi pengecualian lainnya bersama-sama

Video dan slide

  • Topik ini dipresentasikan di NDC London 2014
  • Video lain dari presentasi yang sama juga tersedia
  • Tersedia slide presentasi Functional Programming eXchange pada 14 Maret 2014
  • Slide PowerPoint juga dapat diunduh dari GitHub, dan disebutkan bahwa slide tersebut bebas dipinjam dan digunakan

Monad Either dan komposisi Kleisli

  • Bagi pengguna Haskell, pendekatan ini mungkin terlihat seperti tipe Either
  • Di sini pendekatan tersebut dispesialisasikan ke bentuk yang memakai daftar tipe error kustom pada kasus Left
    • Contoh tipenya adalah type TwoTrack a b = Either [a] (b,[a])
  • Hal baru yang dibuat bukanlah konsep penanganan error itu sendiri, melainkan lebih dekat pada analogi railway untuk menjelaskannya
  • Alasannya jelas mengapa istilah standar Haskell tidak ditempatkan di depan
    • Materi ini bukan tutorial monad, melainkan berfokus pada pemecahan masalah penanganan error
    • Banyak orang yang baru mengenal F# tidak akrab dengan monad, sehingga penjelasan yang visual dan tidak terlalu mengintimidasi bisa terasa lebih intuitif
    • Menyebut tipe two-track yang memiliki bind langsung sebagai monad itu tidak akurat, dan hukum monad juga tidak dibahas
    • Either adalah alat yang terlalu umum, jadi di sini yang ingin diberikan adalah resep, bukan sekadar alatnya

Komponen resep penanganan error

  • Pendekatan ini adalah kumpulan teknik yang lebih luas daripada sekadar “pakai Either dan bind
  • Teknik yang termasuk di dalamnya adalah sebagai berikut
    • Menggunakan daftar tipe error kustom di sisi kiri dan kanan, alih-alih bentuk sederhana seperti Either String a
    • Mengintegrasikan fungsi monadik ke dalam pipeline dengan bind(>>=)
    • Mengomposisikan fungsi monadik dengan komposisi Kleisli (>=>)
    • Mengintegrasikan fungsi non-monadik ke dalam pipeline dengan map(fmap)
    • Karena F# tidak menggunakan monad IO, tee dipakai untuk mengintegrasikan fungsi unit ke dalam pipeline
    • Memetakan exception ke kasus error
    • Menggabungkan fungsi monadik secara paralel dengan &&& dalam situasi seperti validasi
    • Memanfaatkan kelebihan tipe error kustom dalam domain-driven design
    • Perluasan seperti logging, domain event, dan transaksi kompensasi
  • Tujuannya adalah membuat template yang cukup serbaguna untuk dipakai hampir di semua situasi, tetapi juga cukup membatasi untuk memaksakan gaya yang konsisten
  • Jika cara menulis kode pada akhirnya nyaris selalu mengerucut ke satu bentuk, orang yang memelihara kode itu nanti akan lebih mudah cepat memahami strukturnya
  • Pendekatan ini bukan satu-satunya solusi, tetapi bisa menjadi titik awal yang baik
  • Bahkan di komunitas Haskell sendiri, cara menangani error tidak konsisten sehingga bisa membingungkan bagi pemula

Cara menerapkannya ke kode saya

  • Jika membutuhkan pustaka F# yang sudah siap, Anda bisa melihat Chessie project yang dapat digunakan bersama NuGet
  • Contoh web service yang menerapkan teknik ini disediakan sebagai proyek GitHub
  • Contoh penerapan pendekatan ROP pada FizzBuzz dapat dilihat di Railway Oriented Programming: carbonated
  • F# tidak memiliki type class, jadi tidak ada cara umum untuk menggunakan kembali monad
  • FSharpX library menawarkan pendekatan yang berguna, tetapi pustaka Rop.fs mendefinisikan semua fungsi langsung dari awal
  • Isolasi seperti ini punya keuntungan karena sama sekali tidak memiliki dependensi eksternal

Bacaan lanjutan

2 komentar

 
GN⁺ 2023-08-20
Komentar Hacker News
  • Elixir menangani masalah ini dengan cukup rapi lewat keyword/makro with
    Seperti with {:ok, file_handle} <- File.open(filename), ... do {:ok, parsed} end, fungsi dijalankan dari atas ke bawah, dan jika setiap nilai balik tidak cocok dengan pola di sebelah kiri, ia mengembalikan lebih awal dengan nilai tersebut
    Jadi tanpa perlu membuat setiap fungsi menerima baik {:ok, value} maupun {:error, reason}, fungsi cukup menerima nilai yang menjadi perhatiannya, sementara pattern matching pada blok with menangani propagasi error
    Misalnya, jika File.open mengembalikan {:error, reason}, IO.read tidak dijalankan dan hasil keseluruhan with menjadi {:error, reason}. Pada akhirnya, kita cukup menulis jalur sukses, dan jalur gagal bisa dicocokkan oleh pemanggil jika diinginkan

    • Kalau “mengembalikan lebih awal apa pun yang tidak cocok”, saya penasaran apa yang terjadi ketika semua nilai itu bertipe berbeda. Jika tipenya dinamis, bukankah kita harus melihat tipenya dan menebak di mana kegagalannya terjadi?
      Rasanya seperti menciptakan ulang exception handling setengah jalan; kalau saja dinamai else alih-alih catch, itu hanya membuatnya tampak tidak terlalu seperti exception handling
    • Sejauh yang saya tahu, bentuk ini dipengaruhi oleh where di Haskell dan let di Lisp
      Ini seperti menetapkan precondition untuk sebuah fungsi, dan di Elixir kita juga bisa memakai blok else
    • Saya penasaran apa kelebihannya dibanding exception. Dan jika IO.read gagal, siapa yang menutup file handle juga menjadi masalah
  • Beberapa tahun kemudian, penulis juga menulis artikel lanjutan berjudul Against Railway Oriented Programming: https://fsharpforfunandprofit.com/posts/against-railway-orie...
    Pemrograman berorientasi rel memang menarik dan ada tempat untuk menggunakannya, tetapi menurut saya perlu disertai peringatan besar. Dalam praktiknya, saya sering melihatnya dipakai untuk menciptakan ulang penanganan exception dengan cara yang buruk; jika exception dipahami dan digunakan dengan benar, sebagian besar kondisi error bisa ditangani dengan lebih rapi dan efektif
    Keunggulan exception adalah, dalam kebanyakan kasus, pilihan yang aman menjadi default. Kondisi error adalah sinyal bahwa kode tidak bisa melakukan tugasnya sesuai spesifikasi, dan jika tetap dilanjutkan dalam keadaan itu, data bisa rusak karena asumsi yang keliru. Secara default, exception akan melakukan pembersihan lalu dipropagasikan ke pemanggil, sedangkan pendekatan seperti pemrograman berorientasi rel membutuhkan banyak boilerplate yang mudah terlupa dan mudah salah
    Namun ada dua kasus ketika pendekatan ini berguna. Pertama, ketika error yang diharapkan bersifat spesifik dan terdefinisi dengan baik, seperti validasi, harus ditangani di titik kemunculannya. Kedua, ketika berada di lingkungan yang tidak bisa memakai penanganan exception. Kode asinkron berbasis Promise pada jQuery lama hampir menyerupai implementasi pemrograman berorientasi rel, tetapi di JavaScript modern dengan async/await, penanganan exception bisa dipakai lagi

    • Hal yang kurang memuaskan dari exception di bahasa arus utama adalah penanganan ini terjadi secara tidak terlihat. Tidak mudah mengetahui pada waktu kompilasi exception apa saja yang bisa muncul di dalam suatu fungsi
      Imbalan besar dari pemrograman berorientasi rel adalah hanya dengan melihat signature fungsi, kita bisa langsung tahu error apa saja yang mungkin muncul
      Boilerplate bisa dikurangi tergantung bahasanya. Haskell punya notasi do, dan di F# kita bisa menulisnya dengan sangat rapi memakai result computation expression. Misalnya, kita bisa mendefinisikan LoginError = InvalidUser | InvalidPwd | Unauthorized of AuthError, lalu menyusun alurnya dengan Result.requireSome, Result.requireTrue, dan Result.mapError
      Sebaliknya, saya juga penasaran apakah kelemahan exception bisa dikurangi. Saya ingin tahu apakah ada linter atau alat analisis statis yang terintegrasi dengan Java IDE dan secara otomatis menampilkan exception yang tidak tertangkap yang bisa dilempar dari baris kode tertentu
      https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoo...
    • Menurut saya, cara implementasi exception tradisional cukup buruk
      Pertama, banyak bahasa memaksa scope yang canggung dan tidak perlu hanya untuk menangkap exception. Misalnya, ketika ingin mendeklarasikan dan menginisialisasi variabel dari hasil fungsi yang bisa melempar exception, lalu memberi nilai lain saat gagal, deklarasi dan inisialisasi harus dipisahkan
      Masalah yang lebih besar adalah kondisi error seharusnya ditangani pada level terendah yang memiliki konteks cukup untuk menanganinya dengan benar, sementara default exception adalah “lempar terus sampai paling atas”. Tambahkan satu cache file saja, lalu fungsi HandleRequest di atas tiba-tiba bisa melempar exception I/O; abstraksi jadi mudah bocor
      Menurut saya, semua hal yang bisa dikembalikan oleh sebuah fungsi harus menjadi bagian eksplisit dari signature, dan pemanggil harus menanganinya secara eksplisit atau setidaknya menandai bahwa ia meneruskannya ke atas. Bahasa tidak perlu menuntut banyak boilerplate untuk itu
    • Menurut saya, Rust menunjukkan dengan baik bahwa pemrograman berorientasi rel dengan dukungan sintaks kelas satu bisa hampir menghilangkan boilerplate. Secara pribadi, tipe/trait Result di Rust benar-benar berhasil menangani error dengan baik
    • Perbedaan antara error sebagai nilai dan exception paling berguna saat menjelaskan error spesifik domain. Error domain memang tepat direpresentasikan sebagai nilai, sedangkan masalah yang tidak terkait domain, seperti database tidak terhubung, terasa lebih alami sebagai exception
      Saat file atau record tidak ada pun, jawabannya bergantung pada apakah itu diharapkan. Jika permintaan melakukan lookup memakai ID yang diberikan pengguna, lookup itu sendiri adalah validasi input pengguna. Namun jika kita melakukan lookup ke storage memakai nama blob yang tersimpan di record database dan blob-nya tidak ada, itu situasi yang cocok ditangani sebagai exception
      Batas antara error dan exception memang kabur dan bergantung pada tim serta ranah masalahnya, tetapi menurut saya ini aturan praktis yang cukup baik
    • Selain error tingkat sistem, saya ingin tahu contoh kondisi error yang bukan “error yang diharapkan, spesifik, dan terdefinisi dengan baik yang harus ditangani di titik kemunculannya”. Saya ingin memahami apakah alasan exception lebih baik dalam kasus seperti itu berasal dari sifat masalahnya, atau dari batasan runtime
      Dalam kode C++, kita bisa memeriksa data di awal, membuat aplikasi crash jika pemeriksaan gagal, lalu men-debug dan memperbaiki kode pemeriksaan awal berdasarkan kondisi error yang ditemukan
      Karena data yang ditangani adalah data yang relatif jelas, mengikuti format data CAD atau topologi geometri yang diketahui, kondisi error terasa “cukup mudah” ditangani dalam arti kita bisa lebih dulu memahami seperti apa data yang benar
  • Sejujurnya, seluruh situs ini adalah gudang harta. Meski tidak berniat memakai bahasa fungsional, situs ini memberi sudut pandang lain, dan itu sangat membantu saya secara pribadi. Saya juga merekomendasikan tulisan-tulisan lainnya

    • Seri tentang membuat parser combinator dari nol adalah salah satu materi paling bernilai yang pernah saya baca dan ikuti. Banyak konsep dan mekanisme dari sana sangat berguna saat menangani bahasa bertipe dengan gaya fungsional
      Meski begitu, sepertinya saya tetap tidak akan pernah benar-benar paham apa itu monad
      https://fsharpforfunandprofit.com/series/understanding-parse...
  • Saya penasaran apakah klaim bahwa “mendorong penanganan error jauh dari titik pemanggilan itu buruk” memang valid. Karena caller-lah yang paling tahu cara menangani error, rasanya seharusnya ditangani langsung, bukan diteruskan ke bawah
    Misalnya dalam alur validate and-then update-db and-then send-email, kegagalan validate mungkin tidak masalah jika dikembalikan ke caller. Namun jika update-db gagal, harus diputuskan apakah akan mencoba lagi, memakai layanan lain, memasukkannya kembali ke queue, atau memberi tahu pengguna. Kegagalan send-email juga sama
    Pada akhirnya, bukankah ini menjadi struktur percabangan yang lebih kompleks, seperti ValidationError diberitahukan ke pengguna, DBError dicoba ulang 5 menit kemudian atau diberi tahu setelah retry jarak jauh, dan EmailFailed memasukkan email kembali ke queue lalu dianggap berhasil. Mungkin saja itu tidak masalah

    • Penanganan error bergaya monad adalah cara untuk mendapatkan keunggulan dari kedua sisi
      Orang yang tidak suka Go mengeluh bahwa kondisi error harus terlalu sering diperiksa, dan checked exception di Java juga mirip. Dengan Either, Anda tidak dipaksa untuk memeriksa; Anda bisa memeriksanya langsung atau meneruskannya ke caller
      Orang yang tidak suka unchecked exception di Java mengeluh bahwa mereka tidak tahu apa yang akan dilempar dan kapan. Dengan Either, hal ini menjadi eksplisit
      Intinya adalah kita tidak tahu apa yang harus dilakukan ketika terjadi error, dan Either cocok dengan ketidaktahuan itu. Dalam codebase Java saat ini, ketika Limit getUserLimit(User) tidak ada, ia bisa melempar exception NotFound, mengembalikan null, atau mengembalikan nilai default, tetapi kita tidak akan tahu sebelum menggali kodenya
      Jika memakai Either, caller bisa mempercayainya tanpa membaca kode internal, dan bisa dengan mudah mengubahnya menjadi kondisi kegagalan lain menggunakan tool bawaan seperti orElse(null) atau orElseThrow(...). Selain itu, karena Either adalah ekspresi, ia bagus untuk diabstraksikan, sehingga kita bisa menulis logika retry umum alih-alih kode retry khusus di dalam DbUpdater
    • Saya mencoba mengimplementasikan logika ini dengan gaya try-catch Java, tetapi hasilnya terlihat jauh lebih buruk, dengan try bertumpuk, catch(DBError), sleep(5), retry jarak jauh, re-queue EmailFailedError, dan notifikasi ValidationError yang saling bercampur
      https://gist.github.com/Andrewp2/9d97bd213b061166d6df565ce26...
      Mungkin ada orang yang bisa menulis versi try-catch yang lebih baik, tetapi menurut saya tetap jauh lebih buruk daripada versi railway-style yang disajikan
    • Penanganan error tingkat rendah dan tingkat tinggi bisa hidup berdampingan, tetapi menurut saya gaya seperti ini lebih cocok untuk contoh di papan tulis atau situasi sekali buat lalu selesai
      Saya suka DDD, tetapi programmer biasa seperti saya membutuhkan siklus maintenance yang masuk akal dan bisa dikelola. TDD bagus pada kode yang dapat berubah karena mengurangi beban kognitif dalam maintenance
      Railway-oriented programming bisa membawa banyak konteks melalui banyak transformasi, dan saya ingin menghindari situasi menerima kereta di ujung rel yang penuh dengan konteks bisnis dan sistem yang belum siap
      Menurut saya DDD dan onion architecture memberikan keunggulan kedua sisi. Pada akhirnya railway tetap muncul, tetapi berada di luar, bukan tertanam di dalam mikro-app. Isu railway-style lebih baik dikodekan secara eksplisit dan sedikit merepotkan; jika dilakukan dengan baik, sebagian besar keputusan railway-style adalah keputusan bisnis yang tidak boleh dihindari dengan struktur coding yang pintar
      Pemrograman bergaya monad memang luar biasa, tetapi ada godaan untuk memakainya demi menghindari keputusan bisnis yang diperlukan. Memasukkan ambiguitas ke dalam type system dapat menyebabkan situasi yang sulit atau mustahil di kemudian hari
    • Penanganan error tingkat rendah dan tingkat tinggi bisa hidup berdampingan. Ini tidak berbeda dengan memiliki try/catch khusus di dalam try/catch besar
      Misalnya, di dalam fungsi send-email, Anda bisa memilih untuk menangani error sementara yang sangat cepat. Jika berhasil terhubung dalam waktu yang diperbolehkan, kembali ke jalur sukses; jika tidak, kembalikan ke caller
      Jika ada aturan yang jelas, penanganan yang membutuhkan intervensi manusia harus selalu dibungkus dan diteruskan ke atas. Meski kembali ke jalur sukses, sebaiknya tetap tinggalkan log peringatan
    • Hasil akhirnya bisa seperti contoh yang Anda berikan, tetapi juga bisa disusun berbeda sambil mempertahankan pipeline top-level dasar yang sama
      notify-user menangani notifikasi pengguna untuk ValidationError dan DBError, try-update-db membungkus update-db, retry 5 menit kemudian, dan retry jarak jauh, sementara try-send-email bisa memasukkan email kembali ke queue saat gagal lalu mengubahnya menjadi sukses
      Maka level teratas tetap seperti validate and-then try-update-db and-then try-send-email or-then notify-user. Sepertinya and/or/then telah dipetakan dengan cukup baik ke map/bind
  • Situs ini adalah salah satu situs edukasi pemrograman terbaik yang pernah saya temui dalam hal mengajarkan konsep yang nyata dan praktis. Saya terutama menyukai konsep membuat state yang salah menjadi tidak dapat direpresentasikan, dan berusaha menerapkannya apa pun bahasanya. Tentu saja, beberapa bahasa membuatnya lebih mudah
    [https://fsharpforfunandprofit.com/posts/designing-with-types...](https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/

  • Jika memakai alur data alih-alih panggilan/pengembalian, masalah ini terselesaikan dengan lebih baik, dan sebagian besar hilang dengan sendirinya
    Dalam model panggilan/pengembalian, karena harus mengembalikan sesuatu, jika tidak ada nilai yang bisa dikembalikan akibat error, kita harus mengembalikan error, atau seperti Go, mengembalikan error bersama nilai normal. Ini mencemari jalur sukses. Meneruskan sisa pemrosesan lewat container polimorfik memang sedikit memperbaiki keadaan, tetapi masalahnya tetap ada
    Dalam alur data, cukup tidak mengirim apa pun ke tahap filter berikutnya, sehingga jalur sukses sama sekali tidak terpengaruh. Error bisa dikirim ke tempat seperti standard error output, lalu dipusatkan di level aplikasi
    Ini mungkin terdengar terlalu bagus, tetapi kami benar-benar memakainya di Wunderlist dan berjalan dengan baik. Teknik yang sama untuk saat nilai tidak ada juga langsung berlaku ketika nilainya belum ada, yaitu pemrosesan asinkron

    • Saya penasaran apakah ini secara fundamental berbeda dari mengembalikan data atau melempar exception agar ditangkap di tempat lain
    • Akan bagus jika ada contoh kode untuk membantu pemahaman
    • Pendekatan itu juga punya kekurangan, dan terasa sangat terbatas. Penanganan exception, terutama exception asinkron, bisa membuat pemulihan error menjadi sulit
      Kalau error terjadi pada stream data sederhana, tidak ada data untuk tahap berikutnya sehingga berhenti itu tidak masalah. Namun jika pada sqrt(-1) langsung “crash” lalu melompat ke output error atau logging, pada use case tertentu akan menjadi sulit ketika kita ingin memperluas domain agar memberikan nilai lain untuk bilangan negatif
      Di sini tipe error eksplisit seperti Maybe berguna. Jalur sukses ditulis seolah-olah tidak ada error, tetapi komposisi fungsi memakai operator lain. Jika ingin pulih dari error, tangani kedua jalur di tempat itu, dan setelahnya bisa saja tidak lagi mengizinkan error. Yang terutama bagus adalah, untuk mengambil nilai nyata dari abstraksi “nilai yang mungkin ada”, kita harus menangani kedua jalur dengan elegan
      Dengan tipe error eksplisit, kita juga bisa keluar dari dikotomi sukses/error. Status seperti “tidak ada nilai, tetapi” atau “ada nilai, tetapi” juga bisa direpresentasikan, dan error dari beberapa pekerjaan yang dapat diproses paralel bisa diakumulasikan lalu dikumpulkan sebagai hasil. Lebih jauh lagi, nondeterminisme bisa dienkode untuk hal-hal seperti penelusuran graf
      Seperti yang dilakukan programmer Haskell, state juga bisa dibawa dengan cara yang murni, dan monad IO Haskell yang terkenal itu lebih mirip perangkat untuk membatasi titik kontak dengan dunia luar ke main, bukan benar-benar merepresentasikan jalur aktual. Itu demi kemurnian, tetapi menurut saya kemurnian tidak selalu merupakan nilai yang harus dikejar secara absolut
      Teknik ini jauh lebih beragam daripada rel kereta sederhana, dan menyediakan cara yang rapi untuk hanya menulis jalur sukses sambil mempertahankan kemurnian. Ini juga menguntungkan untuk analisis statis berbasis persamaan dan unit test tanpa mock. Saya berharap lebih banyak bahasa arus utama menyediakan kotak alat monad seperti ini dengan lebih baik
  • Berikut tautan-tautan terkait: Railway Oriented Programming - https://news.ycombinator.com/item?id=31404643 - Mei 2022, Railway-Oriented Programming (2015) - https://news.ycombinator.com/item?id=17337155 - Juni 2018, Railway oriented programming - https://news.ycombinator.com/item?id=11955917 - Juni 2016, Railway Oriented Programming - https://news.ycombinator.com/item?id=9166943 - Maret 2015, Railway-oriented programming - https://news.ycombinator.com/item?id=7887134 - Juni 2014
    Selain itu, What is railway oriented programming? (2020) - https://news.ycombinator.com/item?id=34245639 - Januari 2023, Railway Oriented Programming in Elixir (2015) - https://news.ycombinator.com/item?id=11958578 - Juni 2016

  • Gaya ini menular seperti monad lain, sehingga semua logika bisnis menjadi berbentuk seperti ini. Jika itu tidak masalah bagi Anda, silakan lanjut membaca
    Exception, sesuai namanya, adalah error yang tidak terduga, dan stack akan di-unwind sampai ke tempat yang menangkapnya sambil secara otomatis menjalankan pembersihan yang diperlukan. Ini cocok untuk error yang terjadi di luar cakupan fungsi bisnis, seperti error I/O atau kehabisan memori
    Jika ingin memvalidasi data, tidak perlu bergantung pada exception handler atau monad. Jadikan hasil pipeline fungsional sebagai data. Jika ada error validasi, kumpulkan ke dalam sebuah set, lalu periksa setelah pipeline selesai. Biasanya besar kemungkinan kita tidak ingin gagal pada error pertama
    Jika pipeline membutuhkan efek samping, jangan lakukan; deskripsikan efek itu sebagai data lalu jalankan nanti. Jika perlu keluar lebih awal di tengah jalan, pisahkan pipeline pada titik itu, proses hasilnya, lalu masukkan ke pipeline lain
    Atau bisa juga memakai rantai interceptor agar memutuskan apakah jalur sukses dilanjutkan. Jika bahasa mendukungnya, gunakan pattern matching. Yang penting adalah keputusan apakah akan melewati sebagian pipeline atau tidak dibuat di luar fungsi bisnis; dengan begitu fungsi bisnis hanya perlu memikirkan transformasi data yang murni, sehingga kemungkinan untuk digunakan ulang menjadi lebih tinggi

  • Senang saja topik seperti ini muncul lagi dan didiskusikan. Saya membaca situs ini selama setahun dan mengikuti contoh-contohnya; ini salah satu materi F# terbaik
    Konsep-konsep yang dibahas di situs ini saja sudah membantu untuk berpikir secara fungsional dalam bahasa apa pun
    Situsnya sudah lama, jadi kalau sekarang mendapat perhatian, saya penasaran apakah itu berarti F# sedang mendapatkan momentum. Dan saya juga penasaran apakah programmer bahasa lain memakai penanganan error ala railway seperti di Rust

    • Latar belakang saya kuat di C# untuk aplikasi desktop, dan seiring waktu gaya coding C# saya makin fungsional. Banyak memakai Linq, kelas immutable, serta tipe return yang bisa memuat informasi error terperinci
      Meski begitu, alasan saya belum bisa pindah ke F# adalah karena sebagian besar waktu saya dihabiskan untuk menyesuaikan frontend desktop. Di area ini, F# sepertinya tidak memberi banyak keuntungan. Frontend desktop yang siap produksi kebanyakan berupa kode berorientasi objek berbasis C#, bukan F# native, dan hanya diikat dengan F# lewat kode glue yang tipis
      Begitu perlu kustomisasi UI yang sedikit saja kompleks, kita harus kembali ke dunia berorientasi objek, dan kalau begitu menurut saya lebih baik langsung melakukannya di C#. Secara pribadi, agar F# mendapat momentum, ia membutuhkan framework UI native
    • Di C++ juga kami memakai pendekatan seperti itu. Kami mendefinisikan tipe return template yang bisa diparameterisasi dengan tipe return T dan enum tertentu yang mendefinisikan kondisi hasil
      Kalau terburu-buru, bisa juga memakai std::pair, atau mengembalikan kalimat error dalam string lalu keluar lebih awal jika string itu tidak kosong. Jika semua fungsi mengembalikan tipe seperti itu, kalimat error bisa di-bubble up sampai level teratas
  • Saya senang tulisan ini muncul lagi setiap beberapa tahun. Ini benar-benar tulisan sekaligus presentasi yang sangat bagus
    Setiap kali membacanya lagi, posisi saya dalam perjalanan teknis—baik dari sisi kemampuan maupun filosofi—sudah berbeda, jadi menarik juga karena selalu terlihat berbeda

    • Saya sangat suka materi seperti ini. Dulu di awal-awal saya menonton presentasi Python berjudul “so you want to be a python expert” setiap 6 bulan selama beberapa tahun, dan setiap kali menontonnya saya makin banyak memahami. Kalau saya tonton lagi sekarang pun mungkin masih ada hal baru yang bisa saya dapatkan