Mengapa penggunaan operator penugasan atau loop tidak disarankan dalam pemrograman fungsional?

9

Jika fungsi saya memenuhi di bawah dua persyaratan, saya percaya berfungsi Sum untuk mengembalikan penjumlahan item dalam daftar di mana item mengevaluasi true untuk kondisi yang diberikan memenuhi syarat untuk disebut sebagai fungsi murni, bukan?

1) Untuk set i / p yang diberikan, o / p yang sama dikembalikan terlepas dari waktu ketika fungsi dipanggil

2) Tidak memiliki efek samping

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if(predicate(item)) result += item;
    return result;
}

Contoh: Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});

Alasan saya mengajukan pertanyaan ini adalah karena saya melihat hampir di mana-mana orang menyarankan untuk menghindari operator penugasan dan loop karena itu adalah gaya pemrograman yang sangat penting. Jadi apa yang salah dengan contoh di atas yang menggunakan loop dan operator penugasan dalam konteks pemrograman fungsi?

rahulaga_dev
sumber
1
Itu tidak memiliki efek samping - itu memiliki efek samping, ketika itemvariabel dimutasi dalam loop.
Fabio
@ Fabio ok. Tetapi bisakah Anda menguraikan ruang lingkup efek samping?
rahulaga_dev

Jawaban:

16

Apa itu dalam pemrograman fungsional yang membuat perbedaan?

Pemrograman fungsional pada prinsipnya bersifat deklaratif . Anda mengatakan apa hasil Anda alih-alih bagaimana cara menghitungnya.

Mari kita lihat implementasi dari snippet Anda yang sangat fungsional. Dalam Haskell akan menjadi:

predsum pred numbers = sum (filter pred numbers)

Apakah jelas apa hasilnya? Cukup begitu, itu adalah jumlah angka yang memenuhi predikat. Bagaimana cara menghitungnya? Saya tidak peduli, tanyakan pada kompiler.

Anda bisa mengatakan bahwa menggunakan sumdan filtermerupakan trik dan itu tidak masuk hitungan. Biarkan menerapkannya tanpa bantuan ini (meskipun cara terbaik adalah menerapkannya terlebih dahulu).

Solusi "Pemrograman Fungsional 101" yang tidak digunakan sumadalah dengan rekursi:

sum pred list = 
    case list of
        [] -> 0
        h:t -> if pred h then h + sum pred t
                         else sum pred t

Masih cukup jelas apa hasilnya dalam hal panggilan fungsi tunggal. Itu bisa 0, atau recursive call + h or 0, tergantung pada pred h. Masih cukup lurus ke depan, bahkan jika hasil akhirnya tidak segera jelas (meskipun dengan sedikit latihan ini benar-benar berbunyi seperti forlingkaran).

Bandingkan dengan versi Anda:

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if (predicate(item)) result += item;
    return result;
}

Apa hasilnya? Oh, saya melihat: lajang returnpernyataan, tidak ada kejutan di sini: return result.

Tapi apa itu result? int result = 0? Sepertinya tidak benar. Anda melakukan sesuatu nanti dengan itu 0. Oke, Anda menambahkannya item. Dan seterusnya.

Tentu saja, bagi sebagian besar programmer ini cukup jelas apa yang terjadi dalam fungsi sederhana seperti ini, tetapi tambahkan beberapa returnpernyataan tambahan dan tiba-tiba semakin sulit untuk dilacak. Semua kode adalah tentang bagaimana , dan apa yang tersisa bagi pembaca untuk mencari tahu - ini jelas gaya yang sangat penting .

Jadi, apakah variabel dan loop salah?

Tidak.

Ada banyak hal yang lebih mudah dijelaskan oleh mereka, dan banyak algoritma yang membutuhkan keadaan bisa berubah menjadi cepat. Tetapi variabel pada dasarnya imperatif, menjelaskan bagaimana alih-alih apa , dan memberikan sedikit prediksi tentang nilai mereka mungkin beberapa baris kemudian atau setelah beberapa iterasi loop. Loop biasanya membutuhkan keadaan yang masuk akal, dan karenanya mereka pada dasarnya juga penting.

Variabel dan loop sama sekali bukan pemrograman fungsional.

Ringkasan

Pemrograman fungsi kontemporsial sedikit lebih gaya dan cara berpikir yang berguna daripada paradigma. Preferensi kuat untuk fungsi murni ada dalam pola pikir ini, tetapi sebenarnya hanya sebagian kecil saja.

Sebagian besar bahasa yang tersebar luas memungkinkan Anda menggunakan beberapa konstruksi fungsional. Misalnya dalam Python Anda dapat memilih antara:

result = 0
for num in numbers:
    if pred(result):
        result += num
return result

atau

return sum(filter(pred, numbers))

atau

return sum(n for n in numbers if pred(n))

Ekspresi fungsional ini cocok untuk masalah semacam itu dan hanya membuat kode lebih pendek (dan lebih pendek bagus ). Anda seharusnya tidak mengganti kode imperatif dengan mereka, tetapi ketika mereka cocok, mereka hampir selalu merupakan pilihan yang lebih baik.

Frax
sumber
thx untuk penjelasan yang bagus !!
rahulaga_dev
1
@RahulAgarwal Anda mungkin menemukan jawaban ini menarik, dengan baik menangkap konsep tangensial membangun kebenaran vs menggambarkan langkah-langkahnya. Saya juga menyukai ungkapan "bahasa deklaratif mengandung efek samping sementara bahasa imperatif tidak" - biasanya program fungsional memiliki pemotongan bersih dan sangat terlihat antara kode stateful berurusan dengan dunia luar (atau menjalankan beberapa algoritma yang dioptimalkan) dan kode fungsional murni.
Frax
1
@Frax: terima kasih !! Saya akan memeriksanya. Baru-baru ini juga muncul pembicaraan Rich Hickey tentang Nilai Nilai. Benar-benar brilian. Saya pikir satu aturan jempol - "bekerja dengan nilai-nilai dan ekspresi daripada bekerja dengan sesuatu yang memegang nilai dan dapat berubah"
rahulaga_dev
1
@ Frx: Juga adil untuk mengatakan bahwa FP adalah abstraksi atas pemrograman imperatif - karena pada akhirnya seseorang harus menginstruksikan mesin tentang "bagaimana melakukan", kan? Jika ya, bukankah pemrograman imperatif memiliki kontrol level yang lebih rendah dibandingkan dengan FP?
rahulaga_dev
1
@Frax: Saya setuju dengan Rahul bahwa imperatif lebih rendah dalam arti lebih dekat ke mesin yang mendasarinya. Jika perangkat keras dapat membuat salinan data tanpa biaya, kita tidak perlu pembaruan yang merusak untuk meningkatkan efisiensi. Dalam pengertian ini, paradigma imperatif lebih dekat dengan logam.
Giorgio
9

Penggunaan keadaan yang bisa berubah umumnya tidak disarankan dalam pemrograman fungsional. Loop dikecilkan sebagai konsekuensinya, karena loop hanya berguna dalam kombinasi dengan keadaan bisa berubah.

Fungsi secara keseluruhan adalah murni, yang hebat, tetapi paradigma pemrograman fungsional tidak hanya berlaku di tingkat keseluruhan fungsi. Anda juga ingin menghindari keadaan tidak bisa berubah juga di tingkat lokal, fungsi di dalam. Dan alasannya pada dasarnya sama: Menghindari keadaan yang bisa berubah membuat kode lebih mudah dipahami dan mencegah bug tertentu.

Dalam kasus Anda, Anda bisa menulis numbers.Where(predicate).Sum()yang jelas lebih sederhana. Dan lebih sederhana berarti lebih sedikit bug.

JacquesB
sumber
Terima kasih !! Saya pikir saya kehilangan garis yang mencolok - tetapi paradigma pemrograman fungsional tidak hanya berlaku di tingkat seluruh fungsi. Tapi sekarang saya juga bertanya-tanya bagaimana memvisualisasikan batas ini. Pada dasarnya dari perspektif konsumen itu adalah fungsi murni tetapi pengembang yang sebenarnya menulis fungsi ini belum mengikuti pedoman fungsi murni? bingung :(
rahulaga_dev
@RahulAgarwal: Batas apa?
JacquesB
Saya bingung dalam pengertian apakah paradigma pemrograman memenuhi syarat sebagai FP dari perspektif fungsi konsumen? Bcoz jika saya melihat implementasi implementasi Wheredi numbers.Where(predicate).Sum()- itu menggunakan foreachloop.
rahulaga_dev
3
@RahulAgarwal: Sebagai konsumen suatu fungsi, Anda tidak terlalu peduli jika suatu fungsi atau modul secara internal menggunakan status yang bisa berubah selama itu murni secara eksternal.
JacquesB
7

Meskipun Anda benar bahwa dari sudut pandang pengamat eksternal, Sumfungsi Anda murni, implementasi internal jelas tidak murni - Anda memiliki status tersimpan di resultmana Anda berulang kali bermutasi. Salah satu alasan untuk menghindari keadaan tidak bisa berubah adalah karena menghasilkan beban kognitif yang lebih besar pada programmer, yang pada gilirannya menyebabkan lebih banyak bug [rujukan?] .

Sementara dalam contoh sederhana seperti ini, jumlah keadaan yang dapat diubah yang disimpan mungkin cukup kecil sehingga tidak akan menyebabkan masalah serius, prinsip umum masih berlaku. Contoh mainan seperti Summungkin bukan cara terbaik untuk menggambarkan keunggulan pemrograman fungsional daripada keharusan - coba lakukan sesuatu dengan banyak keadaan yang bisa berubah dan keuntungannya bisa menjadi lebih jelas.

Philip Kendall
sumber