Pemrograman fungsional adalah apa yang Anda (mungkin) diberitahu. Jika kamu mendengarkan

Saya suka percakapan dengan topik "Saya dulu diberitahu di sekolah / institut / orang tua, tapi sekarang saya tahu." Jika, secara kebetulan, saya mendapati diri saya setidaknya sedikit kompeten dalam hal yang sedang dibahas, maka percakapan seperti itu biasanya mengarah ke salah satu dari tiga pilihan: "di mana Anda pernah mendengar omong kosong seperti itu sebelumnya?" (jika teman bicara itu benar), "dan di mana Anda mendapatkan ini begitu?" (jika dia salah) dan “Anda benar, tetapi ini tidak bertentangan dengan apa yang Anda katakan sebelumnya” (dalam sebagian besar kasus). Saya suka percakapan ini karena alasan berikut: biasanya penggagasnya tidak dibebani dengan pengetahuan awal yang berlebihan tentang masalah ini, yang dalam beberapa kasus memungkinkannya untuk menunjukkan beberapa poin yang diterima sebagai sesuatu yang jelas, tetapi tidak benar-benar seperti itu. Dan salah satu topik untuk percakapan tersebut adalah pemrograman fungsional.

Secara umum, begitu banyak yang telah ditulis dan dikatakan tentang FP sehingga tampaknya semua pertanyaan tentang penerapannya, kesejukan, kinerja, dll. digerogoti sumsum tulang. Namun demikian, pertanyaan seperti itu muncul berulang-ulang, dan akan selalu ada seseorang yang ingin berbicara tentang apa yang Anda semua salah pahami, tetapi kenyataannya seperti itu. Mungkin hari ini saya akan mencoba sendiri peran tidak tahu berterima kasih ini, karena beberapa posting tentang topik lama ini baru-baru ini menarik perhatian saya. Yang pertama dan kedua sekali lagi mengatakan bahwa AF adalah sampah dan mempelajarinya hanya akan merusak karma spesialis masa depan Anda. Lainnya ( satu dan dua) jauh lebih memadai, di dalamnya penulis bertujuan untuk menjelaskan bahwa semua lambda Anda, kombinator, kategori tidak lebih dari debu di mata, dan FP itu sendiri adalah hal yang sederhana, dapat dimengerti dan menyenangkan dalam hidup.

Seberapa benar ini?

Sebelum beralih ke inti masalah, saya akan membuat sedikit penyimpangan dan memberi penekanan. Isi dari dua posting pertama ini, saya pikir benar-benar omong kosong dari seorang spesialis yang buta huruf yang, dengan jari-jarinya yang terbuka, membahas hal-hal yang ia bahkan tidak menghabiskan sedikit waktu yang berharga untuk belajar. Orang-orang baik dari kalangan komentator telah mengindikasikan bahwa ini tidak lebih dari olok-olok. Masalahnya adalah, ternyata, saya tidak dapat memahami tesis yang diajukan dalam terjemahan ini sebagai olok-olok, karena saya harus mendengar sebagian besar dari mereka hidup. Rupanya, Anda dapat mendiagnosis adanya trauma psikologis yang disebabkan oleh kelebihan pasokan omong kosong yang telah melewati otak.

Dua yang kedua lebih cenderung membangkitkan emosi positif, karena di dalamnya penulis menerapkan praktik FP untuk tugas-tugas yang dipahami pengembang OOP. Meskipun ada ketidaksepakatan dengan pesan dasar dari publikasi pertama yang tercermin dalam judul dan keraguan tentang kewajaran penerapan konsep monad dalam bentuk eksplisit dalam bahasa yang berorientasi pada OOP, penulis tidak dapat ditegur karena kurangnya elaborasi materi. Tetapi ada satu aspek dasar, yang tidak bisa saya abaikan. Ini adalah semacam vulgarisasi pemrograman fungsional, suatu upaya untuk menganggapnya sebagai seperangkat alat dan pendekatan sederhana untuk desain program. Yang, menurut saya, tidak sepenuhnya benar.

Oleh karena itu, dalam artikel ini, upaya dilakukan untuk menunjukkan bahwa sifat-sifat program fungsional yang penulis coba untuk mereproduksi dalam kode-nya bukan fondasi pemrograman fungsional, tidak ditetapkan oleh pencipta bijih Haskell dan solusi desain sejenisnya, tetapi juga konsekuensi langsung dari konsep dan model yang benar-benar diletakkan di atas fondasinya, atau, cukup aneh, upaya untuk mengimbangi kekurangan yang dihasilkan yayasan ini.

Jadi intinya


Dalam sains, cukup sering Anda dapat mengamati metamorfosis berikut. Pertama, sebagai bagian dari pertimbangan proses / fenomena / teori tertentu, objek tertentu muncul yang memiliki beberapa sifat penting dan berguna. Tetapi biasanya juga ternyata menjadi agak rumit dalam strukturnya, yang membatasi kegunaan praktisnya. Oleh karena itu, mereka sering bertindak dengan cara ini: mereka mengambil properti dari objek yang diberikan sebagai dasar dan atas dasar ini membangun teori / model / deskripsi baru, di mana objek yang diinginkan menjadi sederhana atau bahkan sepele, atau sifat-sifat inheren yang diperlukan muncul di objek yang lebih sederhana. Sesuatu seperti ini terkait dengan pemrograman fungsional "nyata" dan "elemen pemrograman fungsional", yang tersedia dalam bahasa modern tingkat tinggi.

Karena biasanya berguna untuk membiasakan diri dengan sejarah asal usulnya untuk memahami suatu fenomena, marilah kita mengingat kembali saat-saat sejarah teori komputasi dan pemrograman yang penting untuk pertanyaan kita. Pada akhir abad kesembilan belas dan awal abad kedua puluh ada restrukturisasi yang signifikan dari dasar ilmu matematika. Ini tidak hanya memecahkan sejumlah masalah yang diidentifikasi dan kontradiksi yang merangkak ke inti gagasan pada waktu itu bahwa ada bukti matematika dan matematika, tetapi juga menimbulkan sejumlah pertanyaan baru. Salah satunya adalah sebagai berikut: apa algoritma? Atau, apa yang sama, kelas masalah apa yang dapat dipecahkan sepenuhnya secara mekanis. Saya tidak akan menyebarkan mengapa pertanyaan ini ternyata penting, saya lebih baik langsung ke jawaban yang diberikan oleh Alan Turing, yang dikenal luas di kalangan yang tidak terlalu sempit. Dia merumuskan tesis:"Hanya fungsi yang dapat membuat mesin Turing dapat dihitung." Pernyataan ini tidak terbukti. Faktanya, Turing hanya memberikan definisi formal yang ketat tentang apa yang dianggap sebagai fungsi yang dapat dihitung, konsisten dengan representasi intuitif yang biasanya tertanam dalam konsep ini. Definisi ini terbukti dapat memuaskan pelamar, karena mereka sangat menyadari apa itu mesin, bahkan dengan pita tanpa batas, dan bagaimana fungsinya. Tetapi bagi banyak matematikawan, definisi ini tidak terlalu memuaskan.yang biasanya diinvestasikan dalam konsep ini. Definisi seperti itu mampu memuaskan pelamar, karena mereka sangat menyadari apa itu mesin, bahkan dengan pita tanpa batas, dan bagaimana fungsinya. Tetapi bagi banyak matematikawan, definisi ini tidak terlalu memuaskan.yang biasanya diinvestasikan dalam konsep ini. Definisi ini terbukti dapat memuaskan pelamar, karena mereka sangat menyadari apa itu mesin, bahkan dengan pita tanpa batas, dan bagaimana fungsinya. Tetapi bagi banyak matematikawan, definisi ini tidak terlalu memuaskan.

Tampaknya, konsep yang dioperasikan Turing bagi mereka tampaknya tidak cukup ... abstrak. Dalam hal ini, mereka tidak meninggalkan upaya untuk memberikan definisi yang berbeda, yang akan mencakup kelas fungsi matematika yang lebih besar dan pada saat yang sama masih sesuai dengan ide-ide intuitif kita. Upaya ini tidak membuahkan hasil. Setiap definisi alternatif yang diajukan dan bertahan dari kritik ternyata setara dengan definisi Turing dalam arti bahwa ia menggambarkan kelas fungsi matematika yang persis sama. Namun, studi ini tidak berarti sia-sia. Upaya untuk melihat objek studi dari sudut pandang yang berbeda biasanya jarang sia-sia. Dalam kasus kami, ini menyebabkan munculnya beberapa teori, salah satunya adalah kalkulus lambda yang diusulkan oleh Gereja Alonzo.

Kemalasan adalah mesin kemajuan


Apa yang sangat berguna dalam kalkulus lambda dan mengapa semua orang begitu sibuk dengannya? Semuanya sederhana. Dalam model yang diusulkan oleh Turing, algoritme adalah urutan instruksi yang tidak asing lagi bagi kita, yang lagi-lagi harus dijalankan oleh pemain biasa. Itu intuitif. Tetapi definisi Gereja berbeda. Mekanisme pembangunan utama (dan satu-satunya satu-satunya) dalam kerangka teori ini adalah apa yang disebut istilah lambda, yang dalam istilah kita saat ini dapat (secara kondisional) disebut fungsi anonim. Program (algoritma) dalam hal ini adalah kombinasi dari istilah-istilah yang dibangun sesuai dengan aturan tertentu, data awal adalah nilai-nilai variabel bebas dari istilah lambda, dan proses perhitungan tidak lebih dari pengurangan (penyederhanaan) dari istilah lambda (fungsi), yang dapat dilakukansegera setelah beberapa variabel gratis mendapat nilai. Fakta berikut ternyata tidak terduga di sini: segera setelah variabel menerima nilai - yaitu, segera setelah kami menyajikan bagian dari data awal ke program - kami dapat melakukan pengurangan, tetapi tidak dalam satu, tetapi dengan dua cara. Dalam kasus pertama, proses perhitungan ternyata setara dengan yang direproduksi oleh kalkulator mekanik khas seperti mesin Turing. Aturannya sesuai dengan itu: argumen fungsi harus dihitung sebelum fungsi itu sendiri dihitung. Tetapi ada opsi lain - yang disebut perhitungan parsial. Dalam hal ini, jika hanya sebagian dari argumen yang dihitung, kami masih dapat menghitung (mengurangi) bagian dari fungsi yang hanya menggunakan argumen ini. Pendekatan ini biasanya disebut model komputasi "malas".Berbeda dengan ini, model komputasi Turing kadang-kadang disebut "energik" atau "serakah", bahasa pemrograman yang dibangun atas dasar akan disebut imperatif di bawah ini. Fitur penting dari perhitungan "malas" adalah bahwa jika subrutin ditulis sebagai fungsi, katakanlah, tiga argumen, tetapi dalam kenyataannya hanya menggunakan dua, maka tidak perlu menghitung argumen ketiga ini untuk menghitung nilai fungsi.

Dan ini memberi kita kemungkinan praktis yang menarik. Misalnya, kemampuan untuk bekerja dengan urutan yang tak terbatas. Hal ini tidak sulit bagi siapa saja yang mulai mengenal pemrograman fungsional pada umumnya dan dengan bahasa Haskell khususnya untuk memahami cara ini untuk mendapatkan pertama n angka Fibonacci:

fibonacci2 a b = a : (fibonacci2 b (a+b))
fibonacci = fibonacci2 1 1

nfibonacci n = take n fibonacci

Penjelasan untuk Orang Asing dengan Haskell
fibonacci2 , , fibonacci2 b (a+b). ( !) :
def fibonacci2(a, b) :
    return [a] + fibonacci2(b, a+b)

def fibonacci() :
    return fibonacci2(1, 1)

def nfibonacci(n) :
    res = []
    data = fibonacci()
    for i in range(n) :
      res.append( data[i] )
    return res

nfibonacci.

Fungsi fibonacci (dan inilah tepatnya fungsinya) menghasilkan daftar angka tanpa akhir. Jika kita menggunakan model komputasi yang akrab bagi kita, maka nfibonacci tidak akan pernah berakhir (yang, saya ingat, sangat dapat diterima dan tidak bertentangan dengan gagasan "komputabilitas" -nya). Tetapi jika kita menggunakan model perhitungan "malas", mudah untuk memperhatikan bahwa begitu n mengambil nilai tertentu, untuk mendapatkan nilai fungsi nfibonacci, kita hanya perlu elemen n pertama dari daftar yang merupakan hasil dari fungsi fibonacci . Dalam hal ini, kita dapat bertindak seperti ini: dapatkan item daftar - melakukan reduksi, elemen berikutnya adalah langkah reduksi lain, nargumen ke-10 - reduksi menghasilkan nilai fungsi. Artinya, dalam hal ini kita mendapatkan hasil untuk waktu yang terbatas meskipun ada "loop" dari prosedur untuk membangun daftar angka Fibonacci.

Di sini, seorang pembaca yang berpikiran imperatif sangat bersemangat berseru: " Tapi tunggu, hanya orang bodoh jujur ​​yang akan mengimplementasikan konstruksi daftar angka Fibonacci dengan cara ini! Ada solusi yang jelas yang tidak mengarah ke satu lingkaran."Dan dia, tentu saja, akan benar. Pemindahan bodoh dari sebuah solusi yang melibatkan implementasi model perhitungan" malas "ke dalam sebuah program untuk perhitungan" serakah "sebenarnya bukanlah indikator kecerdasan yang hebat. Jika Anda menawarkan tugas ini kepada seorang programmer yang telah mempertahankan seluruh kehidupan profesionalnya. Loyalitas, katakanlah, untuk bahasa C, maka ia kemungkinan besar akan menawarkan varian dengan satu siklus dengan penghitung dan dua variabel status.

Tetapi intinya bukan angka Fibonacci itu sendiri. Faktanya adalah bahwa aturan untuk membangun urutan dalam contoh ini dipisahkan dari metode pemrosesan elemen-elemennya. Dan ini adalah properti yang berguna yang diinginkan untuk dapat mereproduksi dalam kasus-kasus yang lebih kompleks, ketika elemen-elemen dari urutan diproses dihasilkan dalam cara yang agak rumit dan transfer sederhana dari solusi "langsung" untuk urutan Fibonacci dalam kasus ini tidak efektif dalam waktu, memori, atau hanya mengarah ke kode, pemahaman yang tidak dapat diakses oleh manusia belaka. Aspirasi semacam itu wajar dan dapat diwujudkan, misalnya, melalui penggunaan iterator atau generator. Dalam python, misalnya, kita bisa melakukan ini:

def fibonacci() :
    a = 1
    b = 1
    yield a
    yield b
    while True :
      c = a + b
      yield c
      a = b
      b = c
     
def nfibonacci(n) :
    return [e for e in itertools.islice(fibonacci(), n)]

Di sini fibonacci () adalah generator yang membuat elemen urutan dengan elemen. Dan dalam hal ini, alih-alih fibonacci, mungkin ada fungsi generator dari kompleksitas. Jika kita membawa kode sepenuhnya, termasuk kode kap mesin, kita mendapatkan desain perangkat lunak yang sangat kompleks dan sangat penting. Tetapi versi finalnya cukup "fungsional". Dalam C ++, seseorang dapat melakukan trik serupa dengan memiliki kelas Fibonachi khusus dan iterator untuk itu. Keputusan akan bervariasi tergantung pada fitur bahasa pemrograman dan preferensi programmer, tetapi tujuannya akan tetap sama - untuk membagi di tingkat organisasi program cara untuk membangun urutan panjang yang sebelumnya tidak diketahui dan cara untuk memproses elemen-elemennya.

Perbedaannya adalah bahwa dalam kerangka pendekatan fungsional, organisasi program semacam itu adalah alami dan dipaksakan oleh metode pelaksanaannya, sedangkan dalam kerangka program imperatif itu membutuhkan kerja kreatif tambahan, termasuk penciptaan konsep tambahan dan pola desain.

Kebersihan adalah kunci kesehatan


Properti lain, di mana mereka berbicara tentang pendekatan fungsional untuk pemrograman, adalah "kemurnian" fungsi. Ini adalah tidak adanya efek samping. Yaitu, pemanggilan fungsi dengan argumen yang sama harus mengarah pada hasil yang sama. Penulis posting yang dikutip menjelaskan secara rinci mengapa dalam program yang dijalankan dengan gaya imperatif, properti ini juga diinginkan. Namun, itu tidak lebih dari konsekuensi dari model perhitungan yang digunakan.

Alasan bahwa semua fungsi dalam program fungsional harus bersih adalah sederhana. Dengan asumsi adanya efek samping ini, ternyata urutan di mana argumen fungsi mendapatkan nilainya secara langsung mempengaruhi hasil fungsi. Kita dapat mengatakan bahwa ini juga benar dalam kerangka pendekatan imperatif, tetapi dalam kasus "kemalasan" perhitungan, semuanya jauh lebih buruk. Bahkan jika kita mengasumsikan bahwa argumen fungsi dapat dihitung secara independen dari satu sama lain dalam urutan yang sewenang-wenang, maka "kemalasan" masih menyiratkan bahwa (secara kondisional) tidak semua kode fungsi akan dieksekusi dalam satu duduk. Ini akan dieksekusi dalam bagian-bagian, tergantung pada dua hal - pada kenyataannya, struktur fungsi yang akan disediakan oleh kompiler bersyarat kepada kami, dan urutan di mana kami akan menyajikan fungsi dengan argumennya.

Wajar bagi kita untuk berharap bahwa jika kita pertama mendefinisikan suatu fungsi

def f(x,y) :
  ...

dan setelahnya
def g(x, y) :
  return f(y, x)

maka hasil pemanggilan g (a, b) akan sama dengan hasil pemanggilan f (b, a) untuk setiap nilai yang dapat dihitung secara independen dari a dan b . Tetapi jika f memiliki efek samping yang memengaruhi perhitungan nilai argumen, maka harapan kita bisa tertipu secara brutal . Misalnya, ketika menghitung b , membaca dari file terjadi - dan ketika menghitung f , membaca dari file yang sama juga terjadi. Dalam perhitungan "malas", kami tidak tahu sebelumnya bagian mana dari kode (untuk b atau untuk f) akan dieksekusi terlebih dahulu. Itu artinya kita tidak tahu hasil apa yang akan diberikan program walaupun kita tahu isi file yang seharusnya dibaca. Perilaku seperti itu pada prinsipnya tidak dapat diterima dan karenanya harus dikecualikan secara kategoris. Jadi, dalam kerangka model perhitungan "malas" (tidak terkendali) efek samping dari fungsi harus dilarang .

Jika urutan perhitungan serakah diterapkan, efek sampingnya jauh lebih dapat diprediksi. Untuk ini dan hanya untuk alasan ini mereka diizinkan dalam pemrograman imperatif. Tetapi jika Anda menyalahgunakannya, maka fitur tersebut akan berubah menjadi bug. Jadi, Anda tidak harus menyalahgunakannya. Jadi, sekali lagi, konsep "kemurnian" yang alami dalam pemrograman fungsional sangat diminati di dunia imperatif.

Konsekuensinya, tesis
Program fungsional - program yang terdiri dari fungsi murni
salah jika dilihat sebagai definisi. Ya, program fungsional terdiri dari fungsi "murni", tetapi program yang terdiri dari fungsi murni tidak harus "fungsional" sama sekali. Ini adalah miliknya, tetapi bukan properti yang menentukan.

Namun, ada masalah. Kemampuan untuk menyelamatkan keadaan dan bahkan input-output dangkal adalah hal-hal yang berhubungan langsung dengan efek samping. Dan hidup tanpa mereka penuh dengan rasa sakit dan penderitaan. Timbul pertanyaan: bagaimana mengawinkan efek samping dan perhitungan "malas"? Jawabannya secara umum tidak mungkin. Jawabannya benar - dalam setiap kasus solusi khusus yang memuaskan harus dicari. Ternyata banyak cara mereproduksi perhitungan dengan efek samping tanpa melanggar konsep “kemurnian” perhitungan yang sesuai dengan konsep umum monad, yang dipinjam dari teori kategori. Saya tidak ingin mencoba lagi untuk menjelaskan apa itu dan apa yang dimakan, jika hanya karena dalam hal apa pun itu tidak akan menggantikan (dan dalam pengalaman saya itu bahkan tidak akan menyederhanakan) penjelasan tentang bagaimana variabel keadaan dapat diterapkan secara spesifik,pengecualian dan hal serupa dalam bahasa fungsional "murni". Moral utama adalah bahwa pemrograman imperatif adalah sumber inspirasi untuk fungsional maupun fungsional untuk imperatif. Selain itu, kadang-kadang sebuah ide melewati konsep yang bersaing seperti melalui filter, kembali dalam bentuk yang diubah dan mengarah ke tampilan alat baru.

Apakah monad dibutuhkan di dunia imperatif? Saya tidak memiliki opini yang kuat tentang masalah ini. Penulis inipuasa yakin itu dibutuhkan. Saya cenderung meragukan pernyataan ini, karena penggunaan konsep monad dalam program fungsional biasanya dihubungkan dengan fakta bahwa algoritma tertentu dapat dirumuskan terlepas dari efek samping spesifik apa yang disembunyikan monad ini. Dengan kata lain, jika tipe data yang ditentukan pengguna (hipotetis, belum dibuat oleh manusia) memenuhi persyaratan untuk monad, maka algoritma tertulis untuknya akan bekerja dengan benar. Ini nyaman terutama dalam studi teoritis. Namun ada beberapa nuansa. Pertama, tidak terlalu jelas mengapa bersembunyi di bungkus efek samping efektif dalam bahasa yang mereka adalah fenomena alam. Kedua,ketika menulis program spesifik dengan tipe data spesifik dan arsitektur target spesifik, algoritma umum seperti itu paling sering dipaksa untuk menjalani restrukturisasi untuk meningkatkan produktivitas. Menulis algoritma umum menggunakan monad dalam gaya imperatif adalah mungkin, tetapi kesesuaian pendekatan ini menimbulkan keraguan saya. Fakta bahwa beberapa analog Mungkin dari tipe std :: opsional dari C ++ akan dinyatakan sebagai monad tidak mungkin mempengaruhi praktik penggunaannya.

?


Fungsi tingkat tinggi adalah alat yang begitu banyak digunakan dalam program fungsional sehingga fakta mendukung sesuatu yang serupa dalam beberapa bahasa pemrograman sudah cukup bagi beberapa individu aneh untuk mengenali bahasa ini sebagai fungsional. Apa itu "fungsi tingkat tinggi"? Ini adalah fungsi yang beroperasi pada fungsi lain sebagai argumen, atau mengembalikan fungsi sebagai hasilnya. Tampaknya di sini dapat menyebabkan perdebatan? Ternyata banyak. Untuk memulainya, apa yang umumnya dipahami dengan istilah "fungsi". Pemrogram biasanya beralasan sederhana: jika sesuatu dapat disebut sebagai fungsi, maka itu dapat dianggap sebagai fungsi. Dalam kerangka pendekatan imperatif, ini masuk akal, karena secara intuitif fungsi adalah bahwa untuk serangkaian argumen tertentu ia memberikan hasil tertentu.Jika kita mengakui adanya efek samping, maka dalam arti praktis benar-benar tidak ada perbedaan antara fungsi "normal" bahasa dan, katakanlah, objek kelas memiliki operator kelebihan beban ().

Tetapi dalam pemrograman fungsional, definisi fungsi semacam itu tidak cukup konstruktif, karena tidak memungkinkan untuk menafsirkan konsep perhitungan parsial dari fungsi ini sendiri. Dalam pemrograman fungsional, fungsi bukan "salah satu" elemen struktural dari suatu program, tetapi dalam arti tertentu sebaliknya: semua elemen program adalah fungsi. Karena itu, pada kenyataannya, ini adalah "pemrograman fungsional". Dan sekali lagi, jika semuanya adalah fungsi, yaitu, setiap argumen dari fungsi apa pun adalah fungsi, maka setiap fungsi dengan argumen adalah fungsi tingkat tinggi. Oleh karena itu, fungsi tingkat tinggi adalah elemen alami dari program fungsional. Sedemikian rupa sehingga bahkan alokasi mereka di kelas yang terpisah tidak masuk akal.

Sebagai fungsi urutan yang lebih tinggi, peta atau lipatan biasanya diberikan.. Tetapi kami akan mempertimbangkan fungsi yang lebih sepele dari sembarang argumen dari f (x, y) . Dalam kerangka model perhitungan "malas", argumen fungsi ini akan dihitung hanya ketika mereka benar-benar diperlukan. Misalkan argumen pertama adalah x .

Kami menghitung argumen ini, memberikan nilainya f dan, di samping itu, menghitung semua yang dapat kami hitung tanpa menggunakan nilai argumen y . Kemudian sisa perhitungan dapat direpresentasikan sebagai fungsi baru, sudah independen dari x , misalnya, g (y) . Tetapi dalam kasus ini, tidak ada yang mencegah kita secara formal menyajikan f bukan sebagai fungsi dari dua argumen, tetapi sebagai fungsi dari satu argumenf (x) , yang hasilnya adalah fungsi lain g (y) . Dengan kata lain, dalam kerangka pendekatan fungsional, setiap fungsi argumen N> 1 adalah fungsi urutan yang lebih tinggi, karena dapat diartikan sebagai fungsi dari satu argumen, yang hasilnya adalah fungsi argumen N-1 .

Bisakah kita menerapkan perilaku ini sebagai bagian dari pendekatan imperatif? Tentu saja kita bisa. Dengan python, kita akan menulis sesuatu seperti berikut:

def partial(f, x) :
	def g(*args) :
		return f(x, *args)
	return g

Dengan memanggil fungsi parsial , argumen pertama yang merupakan fungsi argumen N , dan yang kedua adalah nilai argumen pertama, kita mendapatkan fungsi N-1 dari argumen. Sekarang kita bisa menggunakan fungsi baru di mana pun kita bisa menggunakan fungsi argumen N-1 . Artinya, mereka mendapat hal yang sama seperti pada program fungsional. Begitu? Tidak tidak seperti ini. Jika kita berurusan dengan program yang benar-benar fungsional, maka ketika kita memanggil parsial , kita akan menghitung sebagian dari nilai untuk argumen pertama. Dalam beberapa kasus, bahkan mungkin ternyata g adalah nilai konstan. Apa yang kita miliki dalam analog imperatif? Nilai argumen yang dilewati xbaru ingat (ditambahkan ke konteks fungsi g ). Ketika kita memanggil g , nilai x akan dikeluarkan dari nampan dan diganti menjadi f . Artinya, tidak ada perbedaan dalam bentuk, tetapi dalam konten - signifikan.

Menggunakan fungsi dari fungsi itu nyaman karena memungkinkan Anda untuk menggambarkan banyak algoritma penting secara alami. Jadi, mereka diwajibkan untuk tampil dalam bahasa pemrograman imperatif. Dan mereka muncul. Tetapi karena mereka menggunakan model perhitungan yang berbeda, ini akan membutuhkan pengembangan konsep baru. Dan mereka dikembangkan. Misalnya, penutupan yang dijelaskan di atas. Yaitu, fungsi orde yang lebih tinggi dalam bahasa imperatif sesuai dengan apa yang dapat diamati dalam bahasa fungsional, hanya secara eksternal. Tetapi isinya sangat berbeda. Apakah ini penting bagi programmer? Mungkin tidak, tetapi hanya jika dia mengerti dengan baik bagaimana mekanisme itu bekerja yang mengimplementasikan fitur serupa dalam bahasa pemrograman favoritnya. Jika tidak, Anda dapat, misalnya, menerapkan "sebagian aplikasi", tutup ketika membangun fungsi baru (baik, atauapa yang dalam kasus Anda akan terlihat seperti fungsi) tautan dan bukan nilai dan dapatkan perilaku program yang menarik. Dan setelah itu, berteriak tentang inferioritas pendekatan fungsional.

Jadi siapa yang curang pada siapa?


Pada tahap presentasi ini, sangat mungkin untuk meletakkan titik koma dan kembali ke pertanyaan utama. Karena sekarang kita dapat merumuskan pernyataan berikut:

  • Perbedaan utama antara pemrograman fungsional dan pemrograman imperatif bukanlah sifat kemurnian fungsi, keberadaan fungsi anonim, fungsi tingkat tinggi, monad, polimorfisme parametrik, atau apa pun. Perbedaan utama adalah penggunaan model perhitungan yang berbeda. Segala sesuatu yang lain tidak lebih dari konsekuensi.
  • , , . . , «» «» . , . .
  • , , , . . — .
  • , . , «» ; , . , - , - . .
  • , . , . , , , . .

Pemrograman fungsional adalah apa yang Anda (mungkin) diberitahu tentang. Ini adalah pengurangan beta, kombinator titik tetap, monad, pengetikan Hindley-Milner, dan banyak lagi. Jangan bingung pembungkus dengan konten. FP tidak didasarkan pada matematika yang paling sederhana, itu tidak dapat dikuasai untuk beberapa malam dengan segelas teh; tidak mungkin diproyeksikan secara langsung ke masalah dan proyek mendesak Anda, Anda tidak akan mendapatkan keuntungan yang dijamin dan cepat dari pengetahuan ini. Tetapi banyak elemen dari apa yang ada dalam pendekatan fungsional dipinjam, diproses, dan pada akhirnya diimplementasikan dalam bahasa pemrograman yang berorientasi pada pengembangan proyek-proyek besar. Ya, mereka diatur secara berbeda dari leluhur fungsional mereka, tetapi ini tidak membuat mereka kurang berguna. Hanya idiot klinis yang akan menyiarkan pesan serius bahwa Haskell adalah bahasa yang buruk,karena sulit untuk menulis program untuk segala jenis akuntansi. Seseorang dibebani dengan kehadiran kecerdasan, bahkan dari sudut pandang aktivitas profesionalnya, tanpa membenamkan dalam seluk-beluk teori cukup mampu memahami dengan tepat praktik mana dari pemrograman fungsional yang harus diadopsi untuk membuat kode Anda lebih baik. Untuk demonstrasi yang meyakinkan yang saya sampaikan terima kasihPsyhast.

Pelajari pemrograman fungsional. Atas nama dirimu sendiri.

All Articles