Berikut adalah argumen saya mengapa pemrograman fungsional dapat , dan harus digunakan untuk ilmu komputasi. Manfaatnya sangat besar, dan kontra dengan cepat hilang. Dalam pikiranku hanya ada satu con:
Con : kurangnya dukungan bahasa di C / C ++ / Fortran
Setidaknya dalam C ++, con ini menghilang - karena C ++ 14/17 telah menambahkan fasilitas yang kuat untuk mendukung pemrograman fungsional. Anda mungkin harus menulis sendiri kode pustaka / dukungan, tetapi bahasanya adalah teman Anda. Sebagai contoh, berikut adalah pustaka (peringatan: plug) yang melakukan larik multi-dimensi yang tidak dapat diubah di C ++: https://github.com/jzrake/ndarray-v2 .
Juga, di sini ada tautan ke buku bagus tentang pemrograman fungsional dalam C ++, meskipun tidak berfokus pada aplikasi sains.
Berikut ini ringkasan saya tentang apa yang saya yakini sebagai pro:
Pro :
- Kebenaran
- Dapat dimengerti
- Performa
Dalam hal kebenaran , program fungsional secara nyata berpose : mereka memaksa Anda untuk mendefinisikan dengan benar keadaan minimum variabel fisika Anda, dan fungsi yang memajukan status itu ke depan dalam waktu:
int main()
{
auto state = initial_condition();
while (should_continue(state))
{
state = advance(state);
side_effects(state);
}
return 0;
}
Memecahkan persamaan diferensial parsial (atau ODE) sangat cocok untuk pemrograman fungsional; Anda hanya menerapkan fungsi murni ( advance
) ke solusi saat ini untuk menghasilkan yang berikutnya.
Dalam pengalaman saya, perangkat lunak simulasi fisika pada umumnya dibebani oleh manajemen negara yang buruk . Biasanya, setiap tahapan algoritma beroperasi pada beberapa bagian dari keadaan bersama (efektif global). Ini menyulitkan, atau bahkan tidak mungkin, untuk memastikan urutan operasi yang benar, membuat perangkat lunak rentan terhadap bug yang dapat bermanifestasi sebagai kesalahan-kesalahan, atau lebih buruk lagi, istilah kesalahan yang tidak merusak kode Anda, tetapi secara diam-diam membahayakan integritas ilmu pengetahuannya. keluaran. Mencoba untuk mengelola keadaan bersama dalam simulasi fisika juga menghambat multi-threading - yang merupakan masalah untuk masa depan, karena superkomputer bergerak ke arah jumlah inti yang lebih tinggi, dan penskalaan dengan MPI sering kali lebih baik pada ~ 100 ribu tugas. Sebaliknya, pemrograman fungsional membuat paralelisme memori bersama menjadi sepele, karena kekekalan.
Kinerja juga ditingkatkan dalam pemrograman fungsional karena evaluasi algoritma yang malas (dalam C ++, ini berarti menghasilkan banyak jenis pada waktu kompilasi - seringkali satu untuk setiap aplikasi fungsi). Tapi itu mengurangi overhead dari akses dan alokasi memori, serta menghilangkan pengiriman virtual - memungkinkan kompiler untuk mengoptimalkan seluruh algoritma dengan melihat sekaligus semua objek fungsi yang menyusunnya. Dalam praktiknya, Anda akan bereksperimen dengan pengaturan titik evaluasi yang berbeda (di mana hasil algoritma di-cache ke buffer memori) untuk mengoptimalkan penggunaan alokasi CPU vs memori. Ini agak mudah karena lokalitas yang tinggi (lihat contoh di bawah) dari tahapan algoritma dibandingkan dengan apa yang biasanya akan Anda lihat dalam modul atau kode berbasis kelas.
Program fungsional lebih mudah dipahami sejauh mereka meremehkan keadaan fisika. Bukan berarti sintaks mereka mudah dimengerti oleh semua kolega Anda! Penulis harus berhati-hati untuk menggunakan fungsi-fungsi yang disebutkan dengan baik, dan para peneliti pada umumnya harus terbiasa melihat algoritma yang diungkapkan secara fungsional daripada secara prosedural. Saya akui bahwa tidak adanya struktur kontrol dapat membuat tidak nyaman bagi beberapa orang, tetapi saya tidak berpikir itu akan menghentikan kita untuk pergi ke masa depan yang dapat melakukan sains dengan kualitas yang lebih baik di komputer.
Di bawah ini adalah advance
fungsi sampel , diadaptasi dari kode volume hingga menggunakan ndarray-v2
paket. Perhatikan to_shared
operator - ini adalah poin evaluasi yang saya singgung sebelumnya.
auto advance(const solution_state_t& state)
{
auto dt = determine_time_step_size(state);
auto du = state.u
| divide(state.vertices | volume_from_vertices)
| nd::map(recover_primitive)
| extrapolate_boundary_on_axis(0)
| nd::to_shared()
| compute_intercell_flux(0)
| nd::to_shared()
| nd::difference_on_axis(0)
| nd::multiply(-dt * mara::make_area(1.0));
return solution_state_t {
state.time + dt,
state.iteration + 1,
state.vertices,
state.u + du | nd::to_shared() };
}