Apa cara yang baik untuk merancang / menyusun program fungsional besar, terutama di Haskell?
Saya telah melalui banyak tutorial (Write Yourself a Scheme menjadi favorit saya, dengan Real World Haskell dekat kedua) - tetapi sebagian besar program relatif kecil, dan tujuan tunggal. Selain itu, saya tidak menganggap beberapa dari mereka sangat elegan (misalnya, tabel pencarian luas di WYAS).
Saya sekarang ingin menulis program yang lebih besar, dengan lebih banyak bagian yang bergerak - memperoleh data dari berbagai sumber yang berbeda, membersihkannya, memprosesnya dengan berbagai cara, menampilkannya dalam antarmuka pengguna, bertahan, berkomunikasi melalui jaringan, dll. Bagaimana mungkin satu struktur kode terbaik agar dapat dibaca, dipelihara, dan mudah beradaptasi dengan perubahan persyaratan?
Ada cukup banyak literatur yang membahas pertanyaan-pertanyaan ini untuk program imperatif berorientasi objek besar. Ide-ide seperti MVC, pola desain, dll. Adalah resep yang layak untuk mewujudkan tujuan luas seperti pemisahan masalah dan penggunaan kembali dalam gaya OO. Selain itu, bahasa imperatif yang lebih baru memberikan gaya refactoring 'desain saat Anda tumbuh' yang, menurut pendapat pemula saya, Haskell tampaknya kurang cocok.
Apakah ada literatur yang setara untuk Haskell? Bagaimana kebun binatang struktur kontrol eksotis tersedia dalam pemrograman fungsional (monad, panah, aplikatif, dll.) Paling baik digunakan untuk tujuan ini? Praktik terbaik apa yang bisa Anda rekomendasikan?
Terima kasih!
EDIT (ini merupakan tindak lanjut dari jawaban Don Stewart):
@dons disebutkan: "Monads menangkap desain arsitektur utama dalam beberapa tipe."
Saya kira pertanyaan saya adalah: bagaimana seharusnya orang berpikir tentang desain arsitektur utama dalam bahasa fungsional murni?
Pertimbangkan contoh beberapa aliran data, dan beberapa langkah pemrosesan. Saya bisa menulis parser modular untuk aliran data ke sekumpulan struktur data, dan saya bisa menerapkan setiap langkah pemrosesan sebagai fungsi murni. Langkah-langkah pemrosesan yang diperlukan untuk satu bagian data akan tergantung pada nilainya dan yang lainnya. Beberapa langkah harus diikuti oleh efek samping seperti pembaruan GUI atau permintaan basis data.
Apa cara 'Benar' untuk mengikat data dan langkah-langkah parsing dengan cara yang baik? Orang bisa menulis fungsi besar yang melakukan hal yang benar untuk berbagai tipe data. Atau seseorang dapat menggunakan monad untuk melacak apa yang telah diproses sejauh ini dan meminta setiap langkah pemrosesan mendapatkan apa pun yang dibutuhkan berikutnya dari keadaan monad. Atau orang dapat menulis sebagian besar program terpisah dan mengirim pesan (saya tidak suka opsi ini).
Slide yang ditautkannya memiliki peluru Hal yang Kita Butuhkan: "Idiom untuk memetakan desain ke tipe / fungsi / kelas / monad". Apa idiomnya? :)
Jawaban:
Saya berbicara sedikit tentang ini dalam Proyek Besar Rekayasa di Haskell dan dalam Desain dan Implementasi XMonad. Rekayasa dalam skala besar adalah tentang mengelola kompleksitas. Mekanisme penataan kode utama di Haskell untuk mengelola kompleksitas adalah:
Sistem tipe
Profiler
Kemurnian
Pengujian
Monad untuk Penataan
Ketikkan kelas dan tipe eksistensial
Konkurensi dan paralelisme
par
ke program Anda untuk mengalahkan pesaing dengan paralelisme yang mudah dan dapat dikompilasi.Refactor
Gunakan FFI dengan bijak
Pemrograman meta
Pengemasan dan distribusi
Peringatan
-Wall
untuk menjaga kode Anda bersih dari bau. Anda mungkin juga melihat Agda, Isabelle atau Catch untuk jaminan lebih. Untuk serat seperti pengecekan, melihat besar hlint , yang akan menyarankan perbaikan.Dengan semua alat ini, Anda dapat menangani kompleksitas, menghilangkan sebanyak mungkin interaksi antar komponen. Idealnya, Anda memiliki basis kode murni yang sangat besar, yang sangat mudah dipelihara, karena bersifat komposisional. Itu tidak selalu mungkin, tetapi patut bertujuan.
Secara umum: dekomposisikan unit logis sistem Anda ke dalam komponen transparan referensial terkecil yang mungkin, kemudian implementasikan dalam modul. Lingkungan global atau lokal untuk set komponen (atau komponen di dalam) mungkin dipetakan ke monads. Gunakan tipe data aljabar untuk menggambarkan struktur data inti. Bagikan definisi tersebut secara luas.
sumber
Don memberimu sebagian besar perincian di atas, tetapi inilah dua sen saya dari melakukan program stateful yang sangat rumit seperti daemon sistem di Haskell.
Pada akhirnya, Anda tinggal di tumpukan transformator monad. Di bagian bawah adalah IO. Di atas itu, setiap modul utama (dalam arti abstrak, bukan modul-in-a-file) memetakan keadaan yang diperlukan ke dalam lapisan dalam tumpukan itu. Jadi, jika Anda memiliki kode koneksi basis data yang disembunyikan dalam sebuah modul, Anda menulis semuanya menjadi lebih dari satu jenis Koneksi MonadReader m => ... -> m ... dan kemudian fungsi basis data Anda selalu dapat mendapatkan koneksi mereka tanpa fungsi dari yang lain modul harus menyadari keberadaannya. Anda mungkin berakhir dengan satu lapisan yang membawa koneksi database Anda, yang lain konfigurasi Anda, sepertiga berbagai semaphores dan mvars Anda untuk resolusi paralelisme dan sinkronisasi, yang lain menangani file log Anda, dll.
Cari tahu penanganan kesalahan Anda terlebih dahulu . Kelemahan terbesar saat ini untuk Haskell dalam sistem yang lebih besar adalah kebanyakan metode penanganan kesalahan, termasuk yang buruk seperti Maybe (yang salah karena Anda tidak dapat mengembalikan informasi apa pun yang salah; selalu gunakan Yang Baik alih-alih Mungkin kecuali jika Anda benar-benar hanya berarti nilai yang hilang). Cari tahu bagaimana Anda akan melakukannya terlebih dahulu, dan atur adapter dari berbagai mekanisme penanganan kesalahan yang digunakan perpustakaan Anda dan kode lainnya menjadi yang terakhir. Ini akan menyelamatkan Anda dari dunia kesedihan nanti.
Tambahan (diekstrak dari komentar; terima kasih kepada Lii & liminalisht ) -
diskusi lebih lanjut tentang berbagai cara untuk mengiris program besar menjadi monad dalam tumpukan:
Ben Kolera memberikan pengantar praktis yang bagus untuk topik ini, dan Brian Hurt membahas solusi untuk masalah
lift
memasukkan tindakan monadik ke dalam monad khusus Anda. George Wilson menunjukkan cara menggunakanmtl
untuk menulis kode yang bekerja dengan monad apa pun yang mengimplementasikan jenis kacamata yang diperlukan, alih-alih jenis monad khusus Anda. Carlo Hamalainen telah menulis beberapa catatan singkat dan berguna yang merangkum pembicaraan George.sumber
lift
memasukkan tindakan monadik ke dalam monad khusus Anda. George Wilson menunjukkan cara menggunakanmtl
untuk menulis kode yang bekerja dengan monad apa pun yang mengimplementasikan jenis kacamata yang diperlukan, alih-alih jenis monad khusus Anda. Carlo Hamalainen telah menulis beberapa catatan singkat dan berguna yang merangkum pembicaraan George.Merancang program besar di Haskell tidak jauh berbeda dengan melakukannya dalam bahasa lain. Memprogram secara besar-besaran adalah tentang memecah masalah Anda menjadi bagian-bagian yang dapat dikelola, dan bagaimana menyatukannya; bahasa implementasi kurang penting.
Yang mengatakan, dalam desain besar itu bagus untuk mencoba dan memanfaatkan sistem tipe untuk memastikan Anda hanya bisa menyatukan potongan Anda dengan cara yang benar. Ini mungkin melibatkan tipe baru atau tipe hantu untuk membuat hal-hal yang tampaknya memiliki tipe yang sama menjadi berbeda.
Ketika datang ke refactoring kode saat Anda pergi, kemurnian adalah keuntungan besar, jadi cobalah untuk menjaga sebanyak mungkin dari kode murni. Kode murni mudah diperbaiki, karena tidak memiliki interaksi tersembunyi dengan bagian lain dari program Anda.
sumber
Saya belajar pemrograman fungsional terstruktur pertama kali dengan buku ini . Ini mungkin bukan apa yang Anda cari, tetapi untuk pemula dalam pemrograman fungsional, ini mungkin salah satu langkah pertama yang terbaik untuk belajar menyusun program fungsional - independen dari skala. Pada semua level abstraksi, desain harus selalu memiliki struktur yang diatur dengan jelas.
Kerajinan Pemrograman Fungsional
http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/
sumber
where beginner=do write $ tutorials `about` Monads
)Saat ini saya sedang menulis buku dengan judul "Desain Fungsional dan Arsitektur". Ini memberi Anda satu set lengkap teknik bagaimana membangun aplikasi besar menggunakan pendekatan fungsional murni. Ini menjelaskan banyak pola dan ide fungsional sambil membangun aplikasi 'Andromeda' seperti SCADA untuk mengendalikan pesawat ruang angkasa dari awal. Bahasa utama saya adalah Haskell. Buku ini mencakup:
Anda mungkin mengenal kode untuk buku di sini , dan kode proyek 'Andromeda' .
Saya berharap untuk menyelesaikan buku ini pada akhir 2017. Sampai itu terjadi, Anda dapat membaca artikel saya "Desain dan Arsitektur dalam Pemrograman Fungsional" (Rus) di sini .
MEMPERBARUI
Saya membagikan buku saya secara online (5 bab pertama). Lihat posting di Reddit
sumber
Posting blog Gabriel Arsitektur program yang dapat diskala mungkin perlu disebutkan.
Saya sering kaget bahwa arsitektur yang tampak elegan sering cenderung keluar dari perpustakaan yang menunjukkan rasa homogenitas yang bagus, dengan cara bottom-up. Dalam Haskell ini sangat jelas - pola yang secara tradisional akan dianggap "arsitektur top-down" cenderung ditangkap di perpustakaan seperti mvc , Netwire dan Cloud Haskell . Artinya, saya harap jawaban ini tidak akan diartikan sebagai upaya menggantikan yang lain di utas ini, hanya saja pilihan struktural dapat dan idealnya disarikan di perpustakaan oleh para pakar domain. Kesulitan nyata dalam membangun sistem besar, menurut pendapat saya, adalah mengevaluasi perpustakaan-perpustakaan ini berdasarkan "kebaikan" arsitekturalnya versus semua masalah pragmatis Anda.
Seperti yang disebutkan oleh liminalisht dalam komentar, Pola desain kategori adalah posting lain oleh Gabriel pada topik, dalam nada yang sama.
sumber
Saya telah menemukan makalah " Mengajar Arsitektur Perangkat Lunak Menggunakan Haskell " (pdf) oleh Alejandro Serrano berguna untuk berpikir tentang struktur skala besar di Haskell.
sumber
Mungkin Anda harus melangkah mundur dan memikirkan bagaimana menerjemahkan deskripsi masalah ke desain. Karena Haskell sangat tinggi, ia dapat menangkap deskripsi masalah dalam bentuk struktur data, tindakan sebagai prosedur, dan transformasi murni sebagai fungsi. Maka Anda memiliki desain. Pengembangan dimulai ketika Anda mengkompilasi kode ini dan menemukan kesalahan konkret tentang bidang yang hilang, instance yang hilang, dan transformator monadik yang hilang dalam kode Anda, karena misalnya Anda melakukan akses database dari perpustakaan yang memerlukan monad keadaan tertentu dalam prosedur IO. Dan voila, ada programnya. Kompiler memberi makan sketsa mental Anda dan memberikan koherensi pada desain dan pengembangannya.
Sedemikian rupa Anda mendapat manfaat dari bantuan Haskell sejak awal, dan pengkodean adalah alami. Saya tidak akan peduli untuk melakukan sesuatu yang "fungsional" atau "murni" atau cukup umum jika apa yang ada dalam pikiran Anda adalah masalah biasa yang konkret. Saya pikir over-engineering adalah hal paling berbahaya di TI. Hal-hal berbeda ketika masalahnya adalah membuat perpustakaan yang abstrak satu set masalah terkait.
sumber