Saya telah memutuskan untuk mengambil sendiri tugas belajar pemrograman fungsional. Sejauh ini merupakan ledakan, dan saya 'melihat cahaya' seolah-olah. Sayangnya, saya tidak benar-benar tahu ada programmer fungsional yang saya dapat memunculkan pertanyaan. Memperkenalkan Stack Exchange.
Saya mengambil kursus pengembangan web / perangkat lunak, tetapi instruktur saya tidak terbiasa dengan pemrograman fungsional. Dia baik-baik saja dengan saya menggunakannya, dan dia hanya meminta saya untuk membantunya memahami cara kerjanya sehingga dia dapat membaca kode saya lebih baik.
Saya memutuskan cara terbaik untuk melakukan ini adalah dengan menggambarkan fungsi matematika sederhana, seperti meningkatkan nilai ke kekuatan. Secara teori saya bisa dengan mudah melakukan itu dengan fungsi prebuilt, tetapi itu akan mengalahkan tujuan contoh.
Ngomong-ngomong, saya mengalami beberapa kesulitan mencari tahu bagaimana cara mempertahankan nilai. Karena ini adalah pemrograman fungsional saya tidak dapat mengubah variabel. Jika saya harus kode ini secara imperatif, itu akan terlihat seperti ini:
(Berikut ini semua kodesemu)
f(x,y) {
int z = x;
for(int i = 0, i < y; i++){
x = x * z;
}
return x;
}
Dalam pemrograman fungsional, saya tidak yakin. Inilah yang saya pikirkan:
f(x,y,z){
if z == 'null',
f(x,y,x);
else if y > 1,
f(x*z,y-1,z);
else
return x;
}
Apakah ini benar? Saya perlu memegang nilai, z
dalam kedua kasus, tetapi saya tidak yakin bagaimana melakukan ini dalam pemrograman fungsi. Secara teori, cara saya melakukannya, tetapi saya tidak yakin apakah itu 'benar'. Apakah ada cara yang lebih baik untuk melakukannya?
y
.Jawaban:
Pertama-tama, selamat atas "melihat cahaya". Anda telah menjadikan dunia perangkat lunak tempat yang lebih baik dengan memperluas wawasan Anda.
Kedua, sejujurnya tidak ada seorang profesor yang tidak memahami pemrograman fungsional akan dapat mengatakan sesuatu yang berguna tentang kode Anda, selain dari komentar basi seperti "lekukan yang terlihat". Ini tidak terlalu mengejutkan dalam kursus pengembangan web, karena sebagian besar pengembangan web dilakukan menggunakan HTML / CSS / JavaScript. Bergantung pada seberapa besar Anda benar-benar peduli tentang belajar pengembangan web, Anda mungkin ingin berupaya mempelajari alat-alat yang diajarkan profesor Anda (meskipun mungkin menyakitkan - saya tahu dari pengalaman).
Untuk menjawab pertanyaan yang dinyatakan: jika kode imperatif Anda menggunakan loop, maka kemungkinan kode fungsional Anda akan bersifat rekursif.
Perhatikan bahwa algoritma ini sebenarnya kurang lebih identik dengan kode imperatif. Bahkan, orang dapat menganggap loop di atas sebagai gula sintaksis untuk proses rekursif berulang.
Sebagai catatan, tidak perlu nilai
z
kode imperatif atau fungsional Anda. Anda seharusnya menulis fungsi imperatif Anda seperti ini:alih-alih mengubah arti variabel
x
.sumber
pow
kurang tepat. Karena itu,pow 3 3
kembali81
, bukan27
. Seharusnyaelse x * pow x (y-1).
Ini benar-benar hanya tambahan untuk jawaban gardenhead, tapi saya ingin menunjukkan ada nama untuk pola yang Anda lihat: melipat.
Dalam pemrograman fungsional, lipatan adalah cara untuk menggabungkan serangkaian nilai yang "mengingat" nilai antara setiap operasi. Pertimbangkan untuk menambahkan daftar angka secara imperatif:
Kami mengambil daftar nilai
xs
dan keadaan awal dari0
(diwakili olehtotal
dalam hal ini). Kemudian, untuk masing-masingx
dalamxs
, kami menggabungkan nilai itu dengan keadaan saat ini sesuai dengan beberapa operasi penggabungan (dalam hal ini penambahan), dan menggunakan hasilnya sebagai keadaan baru . Intinya,sum_all([1, 2, 3])
setara dengan(3 + (2 + (1 + 0)))
. Pola ini dapat diekstraksi menjadi fungsi urutan yang lebih tinggi , fungsi yang menerima fungsi sebagai argumen:Implementasi
fold
ini masih sangat penting, tetapi dapat dilakukan secara rekursif juga:Disajikan dalam bentuk lipatan, fungsi Anda hanyalah:
... tempat
repeat(x, n)
mengembalikan daftarn
salinanx
.Banyak bahasa, terutama yang diarahkan pada pemrograman fungsional, menawarkan pelipatan di perpustakaan standar mereka. Bahkan Javascript menyediakannya dengan nama
reduce
. Secara umum, jika Anda mendapati diri Anda menggunakan rekursi untuk "mengingat" suatu nilai pada suatu lingkaran, Anda mungkin menginginkan lipatan.sumber
fold(repeat(base, power), 1, *)
scan
pada dasarnya adalah difold
mana alih-alih hanya menggabungkan daftar nilai menjadi satu nilai, itu digabungkan dan masing-masing nilai perantara dimuntahkan kembali sepanjang jalan, menghasilkan daftar semua negara perantara lipatan yang dibuat alih-alih hanya menghasilkan keadaan akhir. Ini dapat diterapkan dalam halfold
(setiap operasi pengulangan adalah).combiner_func
adalah argumen, dansum_all
sedang melewati fungsi anonim (itulambda
sedikit - itu menciptakan nilai fungsi tanpa menamai itu) yang mendefinisikan bagaimana ia ingin menggabungkan dua item bersama.Ini adalah jawaban tambahan untuk membantu menjelaskan peta dan lipatan. Untuk contoh di bawah ini, saya akan menggunakan daftar ini. Ingat, daftar ini tidak dapat diubah, sehingga tidak akan pernah berubah:
Saya akan menggunakan angka dalam contoh saya karena mereka menyebabkan kode mudah dibaca. Ingat juga, lipatan dapat digunakan untuk apa pun yang dapat digunakan untuk loop imperatif tradisional.
Sebuah peta mengambil daftar sesuatu, dan fungsi, dan mengembalikan daftar yang telah dimodifikasi menggunakan fungsi. Setiap item diteruskan ke fungsi, dan menjadi apa pun fungsi kembali.
Contoh termudah dari ini adalah hanya menambahkan nomor ke setiap nomor dalam daftar. Saya akan menggunakan pseudocode untuk menjadikannya agnostik bahasa:
Jika Anda mencetak
numbers2
, Anda akan melihat daftar[3, 4, 5, 6, 7]
mana yang pertama dengan 2 ditambahkan ke setiap elemen. Perhatikan fungsiadd-two
itu diberikan kepadamap
untuk digunakan.Lipatan serupa, kecuali fungsi yang Anda harus berikan harus mengambil 2 argumen. Argumen pertama biasanya akumulator (dalam lipatan kiri, yang paling umum). Akumulator adalah data yang dilewati saat perulangan. Argumen kedua adalah item saat ini dari daftar; seperti di atas untuk
map
fungsi.Jika Anda mencetak,
sum
Anda akan melihat jumlah dari daftar angka: 15.Inilah yang harus dilakukan argumen
fold
:Ini adalah fungsi yang kami beri lipatan. Lipatan akan melewati fungsi akumulator saat ini, dan item saat ini dari daftar. Apapun fungsi yang dikembalikan akan menjadi akumulator baru, yang akan diteruskan ke fungsi lain kali. Ini adalah bagaimana Anda "mengingat" nilai ketika Anda mengulang gaya FP. Saya memberinya fungsi yang mengambil 2 angka dan menambahkannya.
Ini adalah akumulator awal; apa akumulator mulai seperti sebelum item dalam daftar diproses. Saat Anda menjumlahkan angka, berapa totalnya sebelum Anda menambahkan angka apa pun? 0, yang saya sampaikan sebagai argumen kedua.
Terakhir, seperti halnya peta, kami juga memasukkan daftar angka untuk diproses.
Jika lipatan masih tidak masuk akal, pertimbangkan ini. Ketika Anda menulis:
Anda pada dasarnya menempatkan fungsi yang diteruskan antara setiap item dalam daftar, dan menambahkan akumulator awal ke kiri atau kanan (tergantung pada apakah itu lipatan kiri atau kanan), jadi:
Menjadi:
Yang sama dengan 15.
Gunakan
map
ketika Anda ingin mengubah satu daftar menjadi daftar lain, dengan panjang yang sama.Gunakan
fold
ketika Anda ingin mengubah daftar menjadi nilai tunggal, seperti menjumlahkan daftar angka.Sebagaimana @Jorg tunjukkan dalam komentar, "nilai tunggal" tidak perlu sesuatu yang sederhana seperti angka; bisa berupa objek tunggal, termasuk daftar atau tupel! Cara saya sebenarnya memiliki lipatan klik untuk saya adalah dengan mendefinisikan peta dalam bentuk lipatan. Perhatikan bagaimana akumulator adalah daftar:
Jujur, begitu Anda memahami masing-masing, Anda akan menyadari bahwa hampir semua perulangan dapat diganti dengan lipatan atau peta.
sumber
i
tidak pernah didefinisikan), tapi saya pikir Anda memiliki ide yang tepat. Satu masalah dengan contoh Anda adalah: function (x
), hanya dilewatkan satu elemen daftar pada satu waktu, bukan seluruh daftar. Pertama kalix
dipanggil, itu melewati akumulator awal Anda (y
) sebagai argumen pertama, dan elemen pertama sebagai argumen kedua. Lain kali dijalankan,x
akan melewati akumulator baru di sebelah kiri (apa pun yangx
dikembalikan pertama kali), dan elemen kedua dari daftar sebagai argumen kedua.fold
ketika Anda ingin mengubah daftar menjadi nilai tunggal (seperti menjumlahkan daftar angka)." - Saya hanya ingin mencatat bahwa "nilai tunggal" ini bisa rumit… termasuk daftar! Sebenarnya,fold
adalah metode umum iterasi, ia dapat melakukan semua yang dapat dilakukan iterasi. Misalnyamap
dapat dengan mudah dinyatakan sebagaifunc map(f, l) = fold((xs, x) => append(xs, f(x)), [], l)
Di sini, "nilai tunggal" yang dihitung olehfold
sebenarnya adalah sebuah daftar.fold
. Dan itu tidak harus daftar, setiap koleksi yang dapat dinyatakan sebagai kosong / tidak kosong akan dilakukan. Yang pada dasarnya berarti bahwa setiap iterator akan melakukannya. (Saya kira melempar kata "catamorphism" di sana akan terlalu banyak untuk perkenalan pemula, meskipun :-D)Sulit menemukan masalah bagus yang tidak bisa diselesaikan dengan fungsionalitas bawaan. Dan jika itu dibangun, maka itu harus digunakan untuk menjadi contoh gaya yang baik dalam bahasa x.
Di haskell misalnya Anda sudah memiliki fungsi
(^)
di Prelude.Atau jika Anda ingin melakukannya lebih terprogram
product (replicate y x)
Apa yang saya katakan adalah sulit untuk menunjukkan kekuatan gaya / bahasa jika Anda tidak menggunakan fitur yang disediakannya. Namun itu mungkin merupakan langkah yang baik untuk menunjukkan cara kerjanya di belakang layar, tetapi saya pikir Anda harus memberi kode cara terbaik dalam bahasa apa pun yang Anda gunakan dan kemudian membantu orang dari sana untuk memahami apa yang terjadi jika diperlukan.
sumber
product
itu hanyalah fungsi pintasan untukfold
dengan perkalian sebagai fungsinya dan 1 sebagai argumen awalnya, dan itureplicate
adalah fungsi yang menghasilkan iterator (atau daftar; seperti yang saya catat) di atas keduanya pada dasarnya tidak bisa dibedakan dalam haskell) yang memberikan sejumlah output identik. Seharusnya mudah untuk memahami sekarang bagaimana implementasi ini melakukan hal yang sama dengan jawaban @ Jack di atas, hanya menggunakan versi kasus khusus yang telah ditentukan dari fungsi yang sama untuk membuatnya lebih ringkas.