Keunikan bahasa pemrograman fungsional adalah... Bahasa pemrograman fungsional. Penerapan Panas

Bahasa pemrograman fungsional

Properti utama bahasa pemrograman fungsional biasanya dipertimbangkan [ oleh siapa?] berikut ini:

  • singkatnya dan kesederhanaan;

Program dalam bahasa fungsional biasanya jauh lebih pendek dan sederhana dibandingkan program yang sama dalam bahasa imperatif.
Contoh (Hoare quicksort dalam bahasa fungsional abstrak):

Sortir Cepat() =
quickSort() = quickSort(n | n t, n<= h) + [h] + quickSort (n | n t, n >H)

  • pengetikan yang kuat;

Dalam bahasa fungsional, sebagian besar kesalahan dapat diperbaiki pada tahap kompilasi, sehingga tahap debugging dan waktu pengembangan program secara keseluruhan berkurang. Selain itu, pengetikan yang kuat memungkinkan kompiler menghasilkan kode yang lebih efisien dan dengan demikian mempercepat eksekusi program.

  • modularitas;

Mekanisme modularitas memungkinkan Anda membagi program menjadi beberapa bagian (modul) yang relatif independen dengan hubungan yang jelas di antara keduanya. Hal ini menyederhanakan proses desain dan dukungan selanjutnya yang besar sistem perangkat lunak. Dukungan untuk modularitas bukan merupakan properti bahasa pemrograman fungsional, namun didukung oleh sebagian besar bahasa tersebut.

  • fungsi adalah objek perhitungan;

Dalam bahasa fungsional (serta dalam bahasa pemrograman dan matematika secara umum), fungsi dapat diteruskan ke fungsi lain sebagai argumen atau dikembalikan sebagai hasilnya. Fungsi yang mengambil argumen fungsi disebut fungsi atau fungsi tingkat tinggi.

Dalam pemrograman fungsional murni tidak ada operator penugasan, objek tidak dapat diubah atau dimusnahkan, objek baru hanya dapat dibuat dengan menguraikan dan mensintesis objek yang sudah ada. Pengumpul sampah yang ada di dalam bahasa ini akan menangani objek yang tidak perlu. Berkat ini, dalam bahasa fungsional murni, semua fungsi bebas dari efek samping.

  • perhitungan yang ditangguhkan (malas).

Dalam bahasa pemrograman tradisional (seperti C++), pemanggilan suatu fungsi menyebabkan semua argumen dievaluasi. Metode pemanggilan fungsi ini disebut panggilan berdasarkan nilai. Jika ada argumen yang tidak digunakan dalam fungsi tersebut, maka hasil perhitungannya hilang, sehingga perhitungan yang dilakukan sia-sia. Dalam arti tertentu, kebalikan dari call-by-value adalah call-by-need (evaluasi malas). Dalam hal ini, argumen dievaluasi hanya jika diperlukan untuk menghitung hasilnya.

Beberapa bahasa pemrograman fungsional

  • Gofel
  • MLWorks Harlequin
  • Klasifikasi bahasa fungsional

    Sebagai contoh murni Bahasa fungsional dapat diberikan oleh Haskell. Namun, sebagian besar bahasa fungsional adalah hibrida dan berisi properti bahasa fungsional dan imperatif. Contoh nyata adalah bahasa Scala dan Nemerle. Mereka secara organik menggabungkan karakteristik bahasa berorientasi objek dan fungsional. Rekursi ekor dan pengoptimalannya diimplementasikan; fungsi tersebut adalah objek lengkap, yaitu dapat disimpan dalam variabel, diteruskan sebagai argumen ke fungsi lain, atau dikembalikan dari suatu fungsi.

    Bahasa fungsional juga dibagi menjadi ketat Dan longgar. Bahasa non-ketat mencakup bahasa yang mendukung evaluasi malas (F#), yaitu argumen fungsi dievaluasi hanya jika argumen tersebut benar-benar diperlukan saat mengevaluasi fungsi. Contoh mencolok dari bahasa yang tidak ketat adalah Haskell. Contoh bahasa yang ketat adalah Standard ML.

    Beberapa bahasa fungsional diimplementasikan di atas mesin virtual pembentuk platform (JVM, .NET), yaitu aplikasi dalam bahasa ini dapat berjalan di lingkungan runtime (JRE, CLR) dan menggunakan kelas bawaan. Ini termasuk Scala, Clojure (JVM), F#, Nemerle, SML.NET (.NET).

    Tautan

    • http://fprog.ru/ - Majalah “Praktik Pemrograman Fungsional”
    • http://www.intuit.ru/department/pl/funcpl/1/ - Dasar-dasar pemrograman fungsional. L.V. Gorodnyaya
    • http://roman-dushkin.narod.ru/fp.html - Kursus kuliah tentang pemrograman fungsional, diberikan di MEPHI sejak tahun 2001;
    • http://alexott.net/ru/fp/books/ - Tinjauan literatur tentang pemrograman fungsional. Buku-buku dalam bahasa Rusia dan Inggris dipertimbangkan.

    Yayasan Wikimedia.

    2010.

      Lihat apa itu "Bahasa pemrograman fungsional" di kamus lain: Bahasa pemrograman cadel - Bahasa pemrograman fungsional. Topik Panduan Penerjemah Teknis

      Bahasa pemrograman tingkat tinggi yang universal. Bahasa cadel: mengacu pada bahasa deklaratif dari tipe fungsional; dirancang untuk memproses data karakter yang disajikan dalam bentuk daftar. Dasar bahasanya adalah fungsi dan rekursif... ... Kamus Keuangan

      Istilah ini memiliki arti lain, lihat Alice. Alice Semantik: fungsional Jenis eksekusi: kompilasi ke bytecode untuk mesin virtual Muncul di: 2002 ... Wikipedia

      Istilah ini memiliki arti lain, lihat Scala. Kelas Bahasa Scala: Multi-paradigma: fungsi... Wikipedia

      Oz Semantik: fungsional, prosedural, deklaratif, berorientasi objek, komputasi terbatas, model H, komputasi paralel Jenis eksekusi: dikompilasi Muncul di: 1991 Pengarang: Gert Smolka murid-muridnya Rilis... Wikipedia

      AWL (Bahasa Web Alternatif) Kelas bahasa: multi-paradigma: fungsional, prosedural, berorientasi objek Jenis eksekusi: ditafsirkan Muncul pada: 2005 Pengetikan data: dinamis ... Wikipedia

      Istilah ini memiliki arti lain, lihat Leda (arti). Leda adalah bahasa pemrograman multi-paradigma yang dirancang oleh Timothy Budd. Bahasa Leda awalnya dibuat dengan tujuan menggabungkan pemrograman imperatif, berbasis objek... ... Wikipedia

      File Erlang: Logo Erlang.png Semantik: multi-paradigma: pemrograman kompetitif dan fungsional Muncul di: 1987 Penulis: Pengetikan data: ketat, dinamis Implementasi utama: E ... Wikipedia

      Dalam bahasa pemrograman fungsional, blok bangunan utama adalah konsep fungsi matematika. Terdapat perbedaan pemahaman fungsi dalam matematika dan fungsi dalam pemrograman, sehingga C tidak dapat digolongkan serupa... ... Wikipedia

      Python ditemukan pada tahun 1980an dan pembuatannya dimulai pada bulan Desember 1989 oleh Guido van Rossum sebagai bagian dari Pusat Matematika dan Ilmu Komputer di Belanda. bahasa piton dipahami sebagai turunan dari bahasa pemrograman ABC, yang mampu memproses... ... Wikipedia

    Fungsinya adalah abstraksi, yang detail implementasi beberapa tindakan disembunyikan di balik nama terpisah. Serangkaian fungsi yang ditulis dengan baik memungkinkannya digunakan berkali-kali. Perpustakaan Standar Python hadir dengan banyak fungsi siap pakai dan telah di-debug dengan baik, banyak di antaranya cukup fleksibel untuk menangani berbagai macam input. Sekalipun bagian kode tertentu tidak digunakan beberapa kali, namun dalam hal input dan output data cukup otonom, dapat dengan aman dipisahkan menjadi fungsi tersendiri.

    Perkuliahan ini lebih berorientasi pada pertimbangan praktis dibandingkan teori pemrograman fungsional. Namun, bila diperlukan, istilah-istilah yang relevan akan digunakan dan dijelaskan.

    Selanjutnya kita akan melihat secara detail deskripsi dan penggunaan fungsi dalam Python, rekursi, fungsi passing dan return sebagai parameter, pemrosesan sequence dan iterator, serta konsep generator. Akan ditunjukkan bahwa dalam Python, fungsi adalah objek (dan oleh karena itu dapat diteruskan sebagai parameter dan dikembalikan sebagai hasil dari menjalankan fungsi). Selain itu, kita akan membahas bagaimana Anda dapat mengimplementasikan beberapa mekanisme pemrograman fungsional yang tidak memiliki dukungan sintaksis langsung dengan Python, tetapi tersebar luas dalam bahasa pemrograman fungsional.

    Apa itu pemrograman fungsional?

    Pemrograman fungsional adalah gaya pemrograman yang hanya menggunakan komposisi fungsi. Dengan kata lain, ini pemrograman dalam ekspresi daripada perintah imperatif.

    Seperti yang ditunjukkan oleh David Mertz dalam artikelnya tentang pemrograman fungsional dengan Python, "fungsional pemrograman - pemrograman dalam bahasa fungsional (LISP, ML, OCAML, Haskell, ...)", atribut utamanya adalah:

    • “Kehadiran fungsi kelas satu” (fungsi, seperti objek lainnya, dapat ditransfer ke dalam fungsi).
    • Rekursi merupakan struktur kendali utama dalam suatu program.
    • Memproses daftar (urutan).
    • Larangan efek samping dalam fungsi, yang terutama berarti tidak adanya penugasan (dalam bahasa fungsional “murni”)
    • Operator dilarang dan penekanannya adalah pada ekspresi. Daripada menggunakan operator, keseluruhan program idealnya merupakan satu ekspresi dengan definisi yang menyertainya.
    • Pertanyaan kunci: Apa perlu dihitung, bukan Bagaimana.
    • Penggunaan fungsi tingkat yang lebih tinggi (fungsi di atas fungsi di atas fungsi).

    Program fungsional

    Dalam matematika, suatu fungsi menampilkan objek dari kumpulan yang sama ( kumpulan definisi fungsi) ke yang lain ( kumpulan nilai fungsi). Fungsi matematika (disebut membersihkan) “secara mekanis”, mereka dengan jelas menghitung hasil dari argumen yang diberikan. Fungsi murni tidak boleh menyimpan data apa pun di antara dua panggilan. Anda dapat menganggapnya sebagai kotak hitam, yang hanya Anda ketahui fungsinya, tetapi tidak tahu caranya.

    Program gaya fungsional dirancang sebagai komposisi fungsi. Dalam hal ini, fungsi dipahami dengan cara yang hampir sama seperti dalam matematika: fungsi memetakan satu objek ke objek lainnya. Dalam pemrograman, fungsi “murni” adalah cita-cita yang tidak selalu dapat dicapai dalam praktik. Praktis fitur yang berguna biasanya punya efek samping: Menyimpan status di antara panggilan atau mengubah status objek lain. Misalnya, mustahil membayangkan fungsi I/O tanpa efek samping. Sebenarnya, fungsi-fungsi tersebut digunakan demi “efek” ini. Selain itu, fungsi matematika dapat dengan mudah bekerja dengan objek yang memerlukan informasi dalam jumlah tak terbatas (misalnya, bilangan real). Secara umum, komputer

    Bahasa yang mirip dengan bahasa fungsional menggunakan konsep yang tidak terlalu ketat. Suatu fungsi dalam matematika tidak dapat mengubah lingkungan yang memanggilnya dan mengingat hasil kerjanya, tetapi hanya memberikan hasil perhitungan fungsi tersebut. Pemrograman yang menggunakan konsep matematika suatu fungsi menyebabkan beberapa kesulitan, sehingga bahasa fungsional, pada tingkat tertentu, juga memberikan kemampuan yang sangat penting, yang memperburuk desain program (misalnya, kemungkinan perubahan lebih lanjut yang tidak menimbulkan rasa sakit). Perbedaan tambahan dari bahasa pemrograman imperatif adalah sifat deklaratif dari deskripsi fungsi. Teks program dalam bahasa pemrograman fungsional menggambarkan“bagaimana memecahkan suatu masalah”, tetapi tidak menentukan urutan tindakan untuk solusi. Bahasa fungsional pertama yang dirancang adalah Lisp. Pilihan dari bahasa ini banyak digunakan dalam sistem desain berbantuan komputer AutoLISP

    Berikut ini biasanya dianggap sebagai properti utama bahasa pemrograman fungsional:

    • singkatnya dan kesederhanaan;
    • pengetikan yang kuat;
    • modularitas;
    • fungsi adalah objek perhitungan;
    • perhitungan yang ditangguhkan (malas).

    Beberapa bahasa pemrograman fungsional

  • Miranda (keluarga apa?)
  • Tautan

    • http://roman-dushkin.narod.ru/fp.html - Kursus kuliah tentang pemrograman fungsional, diberikan di MEPHI sejak tahun 2001.

    Yayasan Wikimedia.

    Lihat apa itu "Bahasa pemrograman fungsional" di kamus lain:

      Bahasa pemrograman yang memungkinkan Anda menentukan program sebagai sekumpulan definisi fungsi. Dalam bahasa pemrograman fungsional: fungsi bertukar data satu sama lain tanpa menggunakan variabel perantara dan penugasan;... ... Kamus Keuangan

      bahasa fungsional- Bahasa pemrograman yang tindakan pada data dinyatakan sebagai panggilan ke prosedur fungsional. [GOST 19781 90] Subyek dukungan. sistem pemrosesan informasi perangkat lunak EN bahasa fungsional... Panduan Penerjemah Teknis

      Semantik Ruby: multi-paradigma Jenis eksekusi: interpreter Muncul pada: 1995 Penulis: Yukihiro Matsumoto Versi terbaru: 1.9.1 ... Wikipedia

      Bahasa fungsional- 37. Bahasa fungsional Bahasa fungsional Bahasa pemrograman di mana tindakan pada data dinyatakan dalam bentuk panggilan ke prosedur fungsional Sumber: GOST 19781 90: Dukungan perangkat lunak untuk sistem pemrosesan informasi. Syarat dan... ... Buku referensi kamus istilah dokumentasi normatif dan teknis

      File Erlang: Logo Erlang.png Semantik: multi-paradigma: pemrograman kompetitif dan fungsional Muncul di: 1987 Penulis: Pengetikan data: ketat, dinamis Implementasi utama: E ... Wikipedia

      Semantik Skema: fungsional Jenis eksekusi: juru bahasa atau kompiler Muncul pada: 1970 Penulis: Guy Steele dan Gerald Sussman Pengetikan data ... Wikipedia

      Istilah ini memiliki arti lain, lihat Miranda. Miranda adalah bahasa pemrograman fungsional yang dibuat pada tahun 1985 oleh David Turner sebagai bahasa fungsional standar. Ia memiliki sistem tipe polimorfik yang ketat, ... ... Wikipedia

      Hope adalah bahasa pemrograman fungsional yang dikembangkan pada awal tahun 1980an; adalah pendahulunya bahasa Miranda dan Haskell. Majalah Byte, Agustus 1985, pertama kali menerbitkan panduan bahasa Harapan. Contoh program perhitungan... ... Wikipedia

      Istilah ini memiliki arti lain, lihat SASL. SASL adalah bahasa pemrograman yang berfungsi penuh yang dikembangkan oleh David Turner di Universitas St Andrews pada tahun 1972, berdasarkan subset aplikatif ISWIM. Pada tahun 1976... ... Wikipedia

      Istilah ini memiliki arti lain, lihat Scala. Kelas Bahasa Scala: Multi-paradigma: fungsi... Wikipedia

    Buku

    • Pemrograman di Clojure. Praktek penggunaan Lisp di dunia Java, Emerick Ch., Carper B., Grand K.. Mengapa banyak orang memilih Clojure? Ini adalah bahasa pemrograman fungsional yang tidak hanya memungkinkan Anda menggunakan perpustakaan Java, layanan, dan sumber daya JVM lainnya, tetapi juga bersaing dengan...

    String terbalik(String arg) ( if(arg.length == 0) ( return arg; ) else ( return reverse(arg.substring(1, arg.length)) + arg.substring(0, 1); ) )
    Fungsi ini cukup lambat karena memanggil dirinya sendiri berulang kali. Ada kemungkinan kebocoran memori di sini, karena objek sementara dibuat berkali-kali. Tapi ini adalah gaya fungsional. Anda mungkin merasa aneh bagaimana orang bisa memprogram seperti ini. Yah, aku baru saja hendak memberitahumu.

    Manfaat Pemrograman Fungsional

    Anda mungkin berpikir bahwa saya tidak dapat membenarkan fitur mengerikan di atas. Ketika saya pertama kali mulai belajar pemrograman fungsional, saya juga berpikir demikian. Saya salah. Ada argumen yang sangat bagus untuk gaya ini. Beberapa di antaranya bersifat subjektif. Misalnya, pemrogram menyatakan bahwa program fungsional lebih mudah dipahami. Saya tidak akan mengemukakan argumen seperti itu, karena semua orang tahu bahwa kemudahan pemahaman adalah hal yang sangat subyektif. Untungnya bagi saya, masih banyak argumen obyektif.

    Pengujian satuan

    Karena setiap simbol di FP tidak dapat diubah, fungsi tidak memiliki efek samping. Anda tidak dapat mengubah nilai variabel, dan suatu fungsi tidak dapat mengubah nilai di luar cakupannya sehingga memengaruhi fungsi lainnya (seperti yang dapat terjadi pada bidang kelas atau variabel global). Artinya, satu-satunya hasil eksekusi fungsi adalah nilai kembaliannya. Dan satu-satunya hal yang dapat mempengaruhi nilai kembalian adalah argumen yang diteruskan ke fungsi.

    Ini dia, impian biru para penguji unit. Anda dapat menguji setiap fungsi dalam suatu program hanya dengan menggunakan argumen yang diperlukan. Tidak perlu memanggil fungsi dalam urutan yang benar atau membuat ulang keadaan eksternal yang benar. Yang perlu Anda lakukan hanyalah meneruskan argumen yang cocok dengan kasus tepi. Jika semua fungsi dalam program Anda lulus pengujian unit, maka Anda akan lebih yakin dengan kualitas perangkat lunak Anda dibandingkan dengan bahasa pemrograman penting. Di Java atau C++, memeriksa nilai yang dikembalikan saja tidak cukup - fungsi dapat mengubah keadaan eksternal, yang juga harus diperiksa. Tidak ada masalah seperti itu di FP.

    Men-debug

    Jika program fungsional tidak berperilaku seperti yang Anda harapkan, maka debugging adalah hal yang mudah. Anda selalu dapat mereproduksi masalah karena kesalahan dalam fungsi tidak bergantung pada kode asing yang telah dieksekusi sebelumnya. Pada program imperatif, error hanya muncul sebentar. Anda harus melalui sejumlah langkah yang tidak terkait dengan bug, karena fungsinya bergantung pada keadaan eksternal dan efek samping dari fungsi lainnya. Di FP, situasinya jauh lebih sederhana - jika nilai yang dikembalikan salah, maka nilai tersebut akan selalu salah, tidak peduli potongan kode apa yang dieksekusi sebelumnya.

    Setelah Anda mereproduksi kesalahan, temukan sumbernya - tugas sepele. Itu bahkan bagus. Segera setelah Anda menghentikan program, Anda akan melihat seluruh tumpukan panggilan di depan Anda. Anda dapat melihat argumen untuk setiap pemanggilan fungsi, seperti dalam bahasa imperatif. Bedanya, dalam program imperatif hal ini tidak cukup, karena fungsi bergantung pada nilai field, variabel global, dan status kelas lain. Suatu fungsi di FP hanya bergantung pada argumennya, dan informasi ini ada tepat di depan mata Anda! Terlebih lagi, dalam program imperatif, memeriksa nilai yang dikembalikan tidak cukup untuk mengetahui apakah suatu kode berperilaku benar. Anda harus memburu lusinan objek di luar fungsi untuk memastikan semuanya berfungsi dengan benar. Dalam pemrograman fungsional, yang harus Anda lakukan hanyalah melihat nilai kembaliannya!

    Saat Anda menelusuri tumpukan, Anda memperhatikan argumen yang diteruskan dan nilai yang dikembalikan. Setelah nilai kembalian menyimpang dari norma, Anda menelusuri fungsi dan melanjutkan. Ini diulangi beberapa kali hingga Anda menemukan sumber kesalahannya!

    Multithread

    Program fungsional segera siap untuk paralelisasi tanpa perubahan apa pun. Anda tidak perlu khawatir dengan kebuntuan atau kondisi balapan karena Anda tidak memerlukan kunci! Tidak ada satu pun data dalam program fungsional yang diubah dua kali oleh thread yang sama atau thread berbeda. Ini berarti Anda dapat dengan mudah menambahkan thread ke program Anda tanpa harus khawatir tentang masalah yang melekat pada bahasa imperatif.

    Jika demikian halnya, lalu mengapa bahasa pemrograman fungsional sangat jarang digunakan aplikasi multi-thread? Sebenarnya lebih sering dari yang Anda kira. Ericsson telah mengembangkan bahasa fungsional yang disebut Erlang untuk digunakan pada switch telekomunikasi yang toleran terhadap kesalahan dan dapat diskalakan. Banyak yang memperhatikan kelebihan Erlang dan mulai menggunakannya. Kita berbicara tentang sistem telekomunikasi dan kontrol lalu lintas, yang tidak semudah sistem yang dikembangkan di Wall Street. Faktanya, sistem yang ditulis dalam Erlang tidak dapat diskalakan dan diandalkan seperti sistem Java. Sistem Erlang sangat andal.

    Kisah multithreading tidak berakhir di situ. Jika Anda menulis aplikasi single-thread, kompiler masih dapat mengoptimalkan program fungsional untuk menggunakan banyak CPU. Mari kita lihat potongan kode selanjutnya.


    Kompiler bahasa fungsional dapat menganalisis kode, mengklasifikasikan fungsi yang menghasilkan baris s1 dan s2 sebagai fungsi yang memakan waktu, dan menjalankannya secara paralel. Hal ini tidak mungkin dilakukan dalam bahasa imperatif, karena setiap fungsi dapat mengubah keadaan eksternal dan kode yang segera mengikuti panggilan dapat bergantung padanya. Di FP, analisis fungsi otomatis dan mencari kandidat yang cocok untuk paralelisasi adalah tugas yang sepele, seperti inline otomatis! Dalam hal ini, gaya pemrograman fungsional adalah bukti masa depan. Pengembang perangkat keras tidak bisa lagi membuat CPU bekerja lebih cepat. Sebaliknya, mereka meningkatkan jumlah inti dan mengklaim peningkatan empat kali lipat dalam kecepatan komputasi multi-thread. Tentu saja, mereka lupa mengatakan pada saat yang tepat bahwa Anda prosesor baru akan menunjukkan peningkatan hanya pada program yang dikembangkan dengan mempertimbangkan paralelisasi. Jumlahnya sangat sedikit di antara perangkat lunak penting. Tapi 100% program fungsional siap untuk multithreading di luar kotak.

    Penerapan Panas

    Di masa lalu untuk menginstal Pembaruan Windows Saya harus me-restart komputer. Berkali-kali. Setelah menginstal pemutar media versi baru. Ada perubahan signifikan pada Windows XP, namun situasinya masih jauh dari ideal (saya menjalankan Pembaruan Windows di tempat kerja hari ini dan sekarang pengingat yang mengganggu tidak akan meninggalkan saya sendirian sampai saya reboot). DI DALAM sistem Unix model pembaruan lebih baik. Untuk menginstal pembaruan, saya harus menghentikan beberapa komponen, tetapi tidak seluruh OS. Meski situasinya terlihat lebih baik, tapi untuk kelas besar aplikasi server ini masih tidak dapat diterima. Sistem telekomunikasi harus dihidupkan 100% setiap saat, karena jika pembaruan menghalangi seseorang untuk memanggil ambulans, nyawa bisa hilang. Perusahaan-perusahaan Wall Street juga tidak ingin mematikan server selama akhir pekan untuk menginstal pembaruan.

    Idealnya, Anda perlu memperbarui semua bagian kode yang diperlukan tanpa menghentikan sistem pada prinsipnya. Di dunia yang imperatif hal ini tidak mungkin [terjemahan. di Smalltalk itu sangat mungkin]. Bayangkan membongkar kelas Java dengan cepat dan memuat ulang versi baru. Jika kita melakukan ini, maka semua instance kelas akan menjadi tidak berfungsi, karena status yang disimpannya akan hilang. Kami harus menulis kode rumit untuk kontrol versi. Kita harus membuat serialisasi semua instance kelas yang dibuat, kemudian menghancurkannya, membuat instance kelas baru, mencoba memuat data serial dengan harapan migrasi akan berjalan lancar dan instance baru akan valid. Selain itu, kode migrasi harus ditulis secara manual setiap saat. Dan kode migrasi harus menjaga hubungan antar objek. Secara teori tidak apa-apa, namun dalam praktiknya tidak akan pernah berhasil.

    Dalam program fungsional, semua status disimpan di tumpukan sebagai argumen fungsi. Ini membuat penerapan panas menjadi lebih mudah! Pada dasarnya, yang perlu Anda lakukan hanyalah menghitung perbedaan antara kode di server produksi dan versi baru, dan menginstal perubahan pada kode tersebut. Selebihnya akan dilakukan secara otomatis oleh alat bahasa! Jika menurut Anda ini fiksi ilmiah, pikirkan dua kali. Insinyur yang bekerja dengan Erlang telah memperbarui sistem mereka selama bertahun-tahun tanpa menghentikan pekerjaan mereka.

    Bukti dan Optimasi dengan Bantuan Mesin

    Sifat menarik lainnya dari bahasa pemrograman fungsional adalah bahwa bahasa tersebut dapat dipelajari dari sudut pandang matematika. Karena bahasa fungsional merupakan implementasi dari sistem formal, maka semuanya operasi matematika digunakan di atas kertas juga dapat diterapkan pada program fungsional. Kompiler, misalnya, dapat mengubah sepotong kode menjadi bagian yang setara, tetapi lebih efisien, sambil secara matematis membenarkan kesetaraannya. Basis data relasional data telah mengalami optimasi seperti itu selama bertahun-tahun. Tidak ada yang menghalangi Anda untuk menggunakannya teknik serupa dalam program reguler.

    Selain itu, Anda dapat menggunakan matematika untuk membuktikan kebenaran bagian dari program Anda. Jika mau, Anda dapat menulis alat yang menganalisis kode Anda dan secara otomatis membuat pengujian Unit untuk kasus edge! Fungsionalitas ini sangat berharga untuk sistem yang kokoh. Saat mengembangkan sistem pemantauan alat pacu jantung atau manajemen lalu lintas udara, alat tersebut sangat penting. Jika perkembangan Anda bukan di bidang aplikasi yang sangat penting, maka alatnya pemeriksaan otomatis masih akan memberi Anda keuntungan besar dibandingkan pesaing Anda.

    Fungsi Tingkat Tinggi

    Ingat, ketika saya berbicara tentang manfaat KB, saya mengatakan bahwa “semuanya terlihat bagus, tetapi percuma jika saya harus menulis dalam bahasa yang kikuk dan semuanya sudah final.” Ini adalah kesalahpahaman. Penggunaan final dimana-mana terlihat janggal hanya pada bahasa pemrograman imperatif seperti Java. Bahasa pemrograman fungsional beroperasi dengan jenis abstraksi lain, yang membuat Anda lupa bahwa Anda suka mengubah variabel. Salah satu alat tersebut adalah fungsi tatanan yang lebih tinggi.

    Di FP, suatu fungsi tidak sama dengan fungsi di Java atau C. Ini adalah superset - fungsi tersebut dapat melakukan hal yang sama seperti fungsi Java dan bahkan lebih banyak lagi. Katakanlah kita memiliki fungsi di C:

    ke dalam tambah(ke dalam i, ke dalam j) ( kembalikan i + j; )
    Di FP ini tidak sama dengan fungsi C biasa. Mari kita kembangkan kompiler Java kita untuk mendukung notasi ini. Kompiler harus mengubah deklarasi fungsi menjadi kode Java berikut (ingat bahwa ada final implisit di mana-mana):

    Kelas add_function_t ( int add(int i, int j) ( return i + j; ) ) add_function_t add = new add_function_t();
    Simbol tambah sebenarnya bukan sebuah fungsi. Ini adalah kelas kecil dengan satu metode. Sekarang kita bisa meneruskan add sebagai argumen ke fungsi lainnya. Kita dapat menuliskannya ke dalam simbol lain. Kita dapat membuat instance add_function_t saat runtime dan instance tersebut akan dimusnahkan oleh pengumpul sampah jika tidak diperlukan lagi. Fungsi menjadi objek dasar, sama seperti angka dan string. Fungsi yang mengoperasikan fungsi (menganggapnya sebagai argumen) disebut fungsi tingkat tinggi. Jangan biarkan ini membuat Anda takut. Konsep fungsi orde tinggi hampir tidak berbeda dengan Konsep Jawa kelas-kelas yang beroperasi satu sama lain (kita dapat meneruskan kelas ke kelas lain). Kita bisa menyebutnya sebagai “kelas tingkat tinggi,” tapi tidak ada yang peduli dengan hal itu karena Java tidak memiliki komunitas akademis yang kuat di baliknya.

    Bagaimana dan kapan sebaiknya Anda menggunakan fungsi tingkat tinggi? Saya senang Anda bertanya. Anda menulis program Anda sebagai satu bagian kode monolitik besar tanpa mengkhawatirkan hierarki kelas. Jika Anda melihat ada bagian kode yang diulang di tempat berbeda, Anda memindahkannya ke fungsi terpisah (untungnya, sekolah masih mengajarkan cara melakukan ini). Jika Anda memperhatikan bahwa beberapa logika dalam fungsi Anda harus berperilaku berbeda dalam beberapa situasi, maka Anda membuat fungsi tingkat tinggi. Bingung? Di Sini contoh nyata dari pekerjaanku.

    Misalkan kita memiliki sepotong kode Java yang menerima pesan, mengubahnya dalam berbagai cara dan mentransfernya ke server lain.

    Void handleMessage(Pesan pesan) ( // ... pesan.setClientCode("ABCD_123"); // ... sendMessage(pesan); ) // ... )
    Sekarang bayangkan sistemnya telah berubah, dan sekarang Anda perlu mendistribusikan pesan antara dua server, bukan satu. Semuanya tetap tidak berubah kecuali kode klien - server kedua ingin menerima kode ini dalam format berbeda. Bagaimana kita bisa menghadapi situasi ini? Kami dapat memeriksa ke mana pesan harus dikirim dan mengatur kode klien yang benar berdasarkan itu. Misalnya seperti ini:

    Kelas MessageHandler ( void handleMessage(Pesan pesan) ( // ... if(msg.getDestination().equals("server1") ( msg.setClientCode("ABCD_123"); ) else ( msg.setClientCode("123_ABC") ; ) // ... kirim Pesan (pesan ) // ... )
    Namun pendekatan ini tidak berjalan dengan baik. Saat server baru ditambahkan, fitur tersebut akan berkembang secara linier dan membuat perubahan akan menjadi mimpi buruk. Pendekatan berorientasi objek terdiri dari mengisolasi superkelas umum MessageHandler dan membuat subkelas logika untuk mendefinisikan kode klien:

    Kelas abstrak MessageHandler ( void handleMessage(Pesan pesan) ( // ... msg.setClientCode(getClientCode()); // ... sendMessage(msg); ) string abstrak getClientCode(); // ... ) kelas MessageHandlerOne memperluas MessageHandler ( String getClientCode() ( return "ABCD_123"; ) ) kelas MessageHandlerTwo memperluas MessageHandler ( String getClientCode() ( return "123_ABCD"; ) )
    Sekarang untuk setiap server kita dapat membuat instance dari kelas yang sesuai. Menambahkan server baru menjadi lebih nyaman. Tapi untuk ini uang receh terlalu banyak teks. Saya harus membuat dua tipe baru hanya untuk menambahkan dukungan untuk kode klien yang berbeda! Sekarang mari kita lakukan hal yang sama dalam bahasa kita dengan dukungan untuk fungsi tingkat tinggi:

    Kelas MessageHandler ( void handleMessage(Pesan pesan, Fungsi getClientCode) ( // ... Pesan pesan1 = msg.setClientCode(getClientCode()); // ... sendMessage(msg1); ) // ... ) String getClientCodeOne( ) ( kembalikan "ABCD_123"; ) String getClientCodeTwo() ( kembalikan "123_ABCD"; ) Pengendali MessageHandler = new MessageHandler(); handler.handleMessage(someMsg, getClientCodeOne);
    Kami tidak membuat tipe baru atau mempersulit hierarki kelas. Kami hanya meneruskan fungsi tersebut sebagai parameter. Kami mencapai efek yang sama seperti pada mitra berorientasi objek, tetapi dengan beberapa keunggulan. Kami tidak terikat pada hierarki kelas mana pun: kami dapat meneruskan fungsi lain apa pun ke runtime dan mengubahnya kapan saja, sambil mempertahankan modularitas tingkat tinggi dengan lebih sedikit kode. Intinya, kompiler membuat lem berorientasi objek untuk kita! Pada saat yang sama, semua keunggulan FP lainnya tetap dipertahankan. Tentu saja, abstraksi yang ditawarkan oleh bahasa fungsional tidak berakhir di situ. Fungsi tingkat tinggi hanyalah permulaan

    Kari

    Kebanyakan orang yang saya temui pernah membaca buku Design Patterns by the Gang of Four. Setiap programmer yang menghargai diri sendiri akan mengatakan bahwa buku ini tidak terikat pada bahasa pemrograman tertentu, dan polanya dapat diterapkan pada pengembangan perangkat lunak secara umum. Ini adalah pernyataan yang mulia. Namun sayangnya hal tersebut jauh dari kebenaran.

    Bahasa fungsional sangat ekspresif. Dalam bahasa fungsional, Anda tidak memerlukan pola desain karena bahasanya sangat tingkat tinggi sehingga Anda dapat dengan mudah memulai pemrograman dalam konsep yang menghilangkan semua pola pemrograman yang diketahui. Salah satu polanya adalah Adaptor (apa bedanya dengan Fasad? Sepertinya perlu ada yang mencapnya lebih banyak halaman untuk memenuhi ketentuan kontrak). Pola ini ternyata tidak diperlukan jika bahasanya mendukung kari.

    Pola Adaptor paling sering diterapkan pada unit abstraksi "standar" di Java - kelas. Dalam bahasa fungsional, pola diterapkan pada fungsi. Pola tersebut mengambil sebuah antarmuka dan mengubahnya menjadi antarmuka lain sesuai dengan kebutuhan tertentu. Berikut adalah contoh pola Adaptor:

    Ke dalam kekuatan(ke dalam i, ke dalam j); int persegi(int i) ( kembalikan pow(i, 2); )
    Kode ini mengadaptasi antarmuka fungsi yang menaikkan suatu bilangan ke pangkat sembarang ke antarmuka fungsi yang mengkuadratkan suatu bilangan. Di kalangan akademisi hal ini teknik paling sederhana disebut kari (setelah ahli logika Haskell Curry, yang melakukan serangkaian trik matematika untuk memformalkan semuanya). Karena fungsi digunakan di mana-mana sebagai argumen dalam FP, currying sangat sering digunakan untuk membawa fungsi ke antarmuka yang diperlukan di satu tempat atau tempat lain. Karena antarmuka suatu fungsi adalah argumennya, currying digunakan untuk mengurangi jumlah argumen (seperti pada contoh di atas).

    Alat ini dibangun ke dalam bahasa fungsional. Anda tidak perlu membuat fungsi yang menggabungkan aslinya secara manual. Bahasa fungsional akan melakukan segalanya untuk Anda. Seperti biasa, mari kembangkan bahasa kita dengan menambahkan kari.

    Kotak = int pow(int i, 2);
    Dengan baris ini kita secara otomatis membuat fungsi kuadrat dengan satu argumen. Fungsi baru akan memanggil fungsi pow, menggantikan 2 sebagai argumen kedua. Dari perspektif Java, akan terlihat seperti ini:

    Kelas square_function_t ( int square(int i) ( return pow(i, 2); ) ) square_function_t square = square_function_t() baru;
    Seperti yang Anda lihat, kami hanya menulis pembungkus fungsi aslinya. Di FP, kari hanyalah cara sederhana dan nyaman untuk membuat pembungkus. Anda fokus pada tugas dan kompiler menulis kode yang diperlukan untukmu! Ini sangat sederhana, dan terjadi setiap kali Anda ingin menggunakan pola Adaptor (pembungkus).

    Evaluasi malas

    Evaluasi yang malas (atau tertunda) adalah teknik menarik yang dapat dilakukan setelah Anda memahami filosofi fungsional. Kita telah melihat potongan kode berikut ketika berbicara tentang multithreading:

    String s1 = agakLongOperation1(); String s2 = agakLongOperation2(); String s3 = gabungan(s1, s2);
    Dalam bahasa pemrograman imperatif, urutan komputasi tidak menimbulkan pertanyaan apa pun. Karena setiap fungsi dapat mempengaruhi atau bergantung pada keadaan eksternal, maka perlu untuk menjaga urutan panggilan yang jelas: pertama agakLongOperation1 , lalu agakLongOperation2 , dan digabungkan di akhir. Namun tidak semuanya sesederhana itu dalam bahasa fungsional.

    Seperti yang kita lihat sebelumnya, agakLongOperation1 dan agakLongOperation2 dapat dijalankan secara bersamaan karena fungsinya dijamin tidak mempengaruhi atau bergantung pada keadaan global. Namun bagaimana jika kita tidak ingin menjalankannya secara bersamaan, haruskah kita memanggilnya secara berurutan? Jawabannya adalah tidak. Perhitungan ini hanya boleh dijalankan jika ada fungsi lain yang bergantung pada s1 dan s2 . Kita bahkan tidak perlu menjalankannya sampai kita memerlukannya di dalam concatenate . Jika alih-alih menggabungkan kita mengganti fungsi yang, bergantung pada kondisi, menggunakan salah satu dari dua argumen, maka argumen kedua mungkin tidak dihitung! Haskell adalah contoh bahasa evaluasi yang malas. Haskell tidak menjamin perintah panggilan apa pun (sama sekali!) karena Haskell mengeksekusi kode sesuai kebutuhan.

    Komputasi yang lambat memiliki sejumlah kelebihan dan juga kekurangan. Pada bagian selanjutnya kita akan membahas kelebihannya dan saya akan menjelaskan bagaimana hidup dengan kekurangannya.

    Optimasi

    Evaluasi yang lambat memberikan potensi optimasi yang sangat besar. Kompiler yang malas melihat kode persis seperti yang dipelajari ahli matematika ekspresi aljabar- dapat membatalkan hal-hal tertentu, membatalkan eksekusi bagian kode tertentu, mengubah urutan panggilan agar lebih efisien, bahkan mengatur kode sedemikian rupa untuk mengurangi jumlah kesalahan, sekaligus menjamin integritas program. Ini yang paling banyak keuntungan besar ketika mendeskripsikan suatu program menggunakan primitif formal yang ketat, kodenya akan patuh hukum matematika dan dapat dipelajari metode matematika.

    Mengabstraksi Struktur Kontrol

    Komputasi lambat memberikan abstraksi tingkat tinggi sehingga hal-hal menakjubkan menjadi mungkin. Misalnya, bayangkan menerapkan struktur kontrol berikut:

    Kecuali(stok.isEuropean()) ( sendToSEC(stok); )
    Kami ingin fungsi sendToSEC dijalankan hanya jika stoknya bukan Eropa. Bagaimana Anda bisa menerapkan kecuali ? Tanpa evaluasi yang malas, kita memerlukan sistem makro, tetapi dalam bahasa seperti Haskell hal ini tidak diperlukan. Kita dapat mendeklarasikan kecuali sebagai suatu fungsi!

    Batal kecuali(kondisi boolean, Kode daftar) ( kode if(!kondisi); )
    Perhatikan bahwa kode tidak akan dieksekusi jika condition == true . Dalam bahasa yang ketat, perilaku ini tidak dapat diulangi karena argumen akan dievaluasi sebelumnya kecuali jika dipanggil.

    Struktur Data Tak Terbatas

    Bahasa malas memungkinkan Anda membuat struktur data tanpa batas, yang jauh lebih sulit dibuat dalam bahasa ketat. - hanya saja tidak dengan Python]. Misalnya saja deret Fibonacci. Jelasnya, kita tidak dapat menghitung daftar yang tidak terbatas dalam waktu yang terbatas dan masih menyimpannya dalam memori. Dalam bahasa ketat seperti Java, kita cukup menulis fungsi yang mengembalikan anggota urutan yang berubah-ubah. Dalam bahasa seperti Haskell, kita dapat mengabstraksi dan mendeklarasikan daftar angka Fibonacci yang tak terbatas. Karena bahasanya malas, hanya bagian penting dari daftar yang benar-benar digunakan dalam program yang akan dihitung. Hal ini memungkinkan Anda untuk mengabstraksikan sejumlah besar masalah dan melihatnya dari tingkat yang lebih tinggi (misalnya, Anda dapat menggunakan fungsi untuk memproses daftar pada urutan tak terbatas).

    Kekurangan

    Tentu keju gratis hanya terjadi dalam perangkap tikus. Perhitungan yang lambat mempunyai sejumlah kelemahan. Ini terutama merupakan kekurangan dari kemalasan. Pada kenyataannya, urutan perhitungan langsung seringkali diperlukan. Ambil contoh kode berikut:


    Dalam bahasa malas, tidak ada yang menjamin bahwa baris pertama akan dieksekusi sebelum baris kedua! Ini berarti kita tidak dapat melakukan I/O, kita tidak dapat menggunakan fungsi asli secara normal (bagaimanapun juga, fungsi tersebut perlu dipanggil dalam urutan tertentu untuk memperhitungkan efek sampingnya) dan tidak dapat berinteraksi dengannya dunia luar! Jika kita memperkenalkan mekanisme untuk memerintahkan eksekusi kode, kita akan kehilangan keuntungan dari ketelitian matematis kode tersebut (dan kemudian kita akan kehilangan semua manfaat dari pemrograman fungsional). Untungnya, semuanya tidak hilang. Matematikawan mulai bekerja dan menemukan beberapa teknik untuk memastikan bahwa instruksi dijalankan dalam urutan yang benar tanpa kehilangan semangat fungsionalnya. Kami mendapatkan yang terbaik dari kedua hal tersebut! Teknik tersebut mencakup kelanjutan, monad, dan pengetikan keunikan. Pada artikel ini kami akan bekerja dengan kelanjutan, dan meninggalkan monad dan pengetikan yang tidak ambigu hingga waktu berikutnya. Menariknya, kelanjutan adalah hal yang sangat berguna, yang digunakan tidak hanya untuk menentukan urutan perhitungan yang ketat. Kami akan membicarakan hal ini juga.

    Sekuel

    Kelanjutan dalam pemrograman memainkan peran yang sama dengan The Da Vinci Code dalam sejarah manusia: sebuah pengungkapan mengejutkan tentang misteri terbesar umat manusia. Yah, mungkin tidak persis seperti itu, tapi mereka pasti merobek penutupnya, sama seperti Anda belajar mengambil akar -1 di masa lalu.

    Ketika kita melihat fungsi, kita hanya mengetahui separuh kebenarannya, karena kita berasumsi bahwa suatu fungsi mengembalikan nilai ke fungsi yang memanggilnya. Dalam pengertian ini, kelanjutan adalah generalisasi fungsi. Suatu fungsi tidak harus mengembalikan kontrol ke tempat pemanggilannya, namun dapat kembali ke tempat mana pun dalam program. "Lanjutkan" adalah parameter yang bisa kita teruskan ke suatu fungsi untuk menunjukkan titik kembali. Kedengarannya jauh lebih menakutkan daripada yang sebenarnya. Mari kita lihat kode berikut:

    Int i = tambah(5, 10); int j = persegi(i);
    Fungsi add mengembalikan angka 15, yang ditulis ke i di lokasi pemanggilan fungsi tersebut. Nilai i kemudian digunakan saat memanggil kuadrat. Perhatikan bahwa kompiler yang malas tidak dapat mengubah urutan penghitungan, karena baris kedua bergantung pada hasil baris pertama. Kita dapat menulis ulang kode ini menggunakan Continuation Passing Style (CPS), di mana add mengembalikan nilai ke fungsi kuadrat.

    Int j = tambah(5, 10, persegi);
    Dalam hal ini, add menerima argumen tambahan - fungsi yang akan dipanggil setelah add selesai berjalan. Dalam kedua contoh j akan sama dengan 225.

    Ini adalah teknik pertama yang memungkinkan Anda menentukan urutan eksekusi dua ekspresi. Mari kita kembali ke contoh I/O kita.

    System.out.println("Masukkan Nama Anda : "); Sistem.di.readLine();
    Kedua baris ini tidak bergantung satu sama lain, dan kompiler bebas mengubah urutannya sesuai keinginan. Tetapi jika kita menulis ulang dalam CPS, kita akan menambahkan ketergantungan yang diperlukan, dan kompiler harus melakukan perhitungan satu demi satu!

    System.out.println("Silakan masukkan nama Anda: ", System.in.readLine);
    Dalam hal ini, println harus memanggil readLine , meneruskan hasilnya, dan mengembalikan hasil readLine di akhir. Dalam bentuk ini, kita dapat yakin bahwa fungsi-fungsi ini akan dipanggil secara bergantian, dan bahwa readLine akan dipanggil sama sekali (bagaimanapun juga, kompiler mengharapkan untuk menerima hasil dari operasi terakhir). Dalam kasus Java, println mengembalikan batal. Tetapi jika beberapa nilai abstrak (yang bisa menjadi argumen untuk readLine) dikembalikan, itu akan menyelesaikan masalah kita! Tentu saja, membangun rangkaian fungsi seperti itu sangat mengganggu keterbacaan kode, tetapi hal ini dapat diatasi. Kita dapat menambahkan fitur sintaksis ke bahasa kita yang memungkinkan kita menulis ekspresi seperti biasa, dan kompiler akan secara otomatis merangkai perhitungan. Sekarang kita bisa melakukan perhitungan dalam urutan apapun tanpa kehilangan keunggulan FP (termasuk kemampuan mempelajari program menggunakan metode matematika)! Jika ini membingungkan, ingatlah bahwa fungsi hanyalah turunan dari sebuah kelas dengan satu anggota. Tulis ulang contoh kita sehingga println dan readLine merupakan turunan kelas, ini akan membuatnya lebih jelas bagi Anda.

    Namun manfaat sekuel tidak hanya sampai di situ. Kita dapat menulis seluruh program menggunakan CPS, sehingga setiap fungsi dipanggil dengan parameter tambahan, kelanjutan yang hasilnya diteruskan. Pada prinsipnya, program apa pun dapat diterjemahkan ke dalam CPS jika setiap fungsi diperlakukan sebagai kasus lanjutan yang khusus. Konversi ini dapat dilakukan secara otomatis (pada kenyataannya, banyak kompiler yang melakukannya).

    Segera setelah kita mengubah program ke bentuk CPS, menjadi jelas bahwa setiap instruksi memiliki kelanjutan, suatu fungsi yang hasilnya akan diteruskan, yang dalam program normal akan menjadi titik panggilan. Mari kita ambil instruksi apa pun dari contoh terakhir, misalnya add(5,10) . Dalam program yang ditulis dalam bentuk CPS, jelas apa kelanjutannya - ini adalah fungsi yang akan dipanggil add setelah pekerjaan selesai. Namun apa kelanjutannya jika program non-CPS? Tentu saja kita bisa mengkonversi program ke CPS, tapi apakah ini perlu?

    Ternyata hal tersebut tidak perlu dilakukan. Perhatikan baik-baik konversi CPS kami. Jika Anda mulai menulis kompiler untuknya, Anda akan menemukan bahwa versi CPS tidak memerlukan tumpukan! Fungsi tidak pernah mengembalikan apa pun, dalam pengertian tradisional kata “return”, mereka hanya memanggil fungsi lain, menggantikan hasil perhitungan. Tidak perlu memasukkan argumen ke dalam tumpukan sebelum setiap panggilan dan kemudian memunculkannya kembali. Kita cukup menyimpan argumen di beberapa lokasi memori tetap dan menggunakan jump alih-alih panggilan normal. Kita tidak perlu menyimpan argumen asli, karena argumen tersebut tidak akan diperlukan lagi, karena fungsi tidak mengembalikan apa pun!

    Dengan demikian, program bergaya CPS tidak memerlukan tumpukan, tetapi berisi argumen tambahan berupa fungsi untuk dipanggil. Program gaya non-CPS tidak memiliki argumen tambahan, tetapi menggunakan tumpukan. Apa yang disimpan di tumpukan? Hanya argumen dan penunjuk ke lokasi memori tempat fungsi tersebut harus dikembalikan. Nah, sudahkah Anda menebaknya? Tumpukan menyimpan informasi tentang kelanjutan! Sebuah pointer ke titik kembali pada tumpukan sama dengan fungsi yang dipanggil dalam program CPS! Untuk mengetahui kelanjutan dari add(5,10) cukup ambil return point dari stack.

    Itu tidak sulit. Kelanjutan dan penunjuk ke titik kembali sebenarnya adalah hal yang sama, hanya kelanjutannya yang ditentukan secara eksplisit, dan oleh karena itu mungkin berbeda dari tempat di mana fungsi tersebut dipanggil. Jika Anda ingat bahwa kelanjutan adalah sebuah fungsi, dan fungsi dalam bahasa kita dikompilasi menjadi sebuah instance kelas, maka Anda akan memahami bahwa penunjuk ke titik kembali di tumpukan dan penunjuk ke kelanjutan sebenarnya adalah hal yang sama. , karena fungsi kita (sebagai turunan dari kelas ) hanyalah sebuah pointer. Ini berarti bahwa kapan saja dalam program Anda, Anda dapat meminta kelanjutan saat ini (pada dasarnya informasi dari tumpukan).

    Oke sekarang kita sudah paham kelanjutannya saat ini apa. Apa maksudnya? Jika kita mengambil kelanjutan saat ini dan menyimpannya di suatu tempat, maka kita menyimpan status program saat ini - kita membekukannya. Ini mirip dengan mode hibernasi OS. Objek kelanjutan menyimpan informasi yang diperlukan untuk melanjutkan eksekusi program dari titik di mana objek kelanjutan diminta. sistem operasi melakukan ini pada program Anda sepanjang waktu ketika ia mengalihkan konteks antar thread. Satu-satunya perbedaan adalah semuanya berada di bawah kendali OS. Jika Anda meminta objek kelanjutan (dalam Skema ini dilakukan dengan memanggil fungsi panggilan-dengan-kelanjutan saat ini), maka Anda akan menerima objek dengan kelanjutan saat ini - tumpukan (atau dalam kasus CPS, fungsi panggilan berikutnya ). Anda dapat menyimpan objek ini ke variabel (atau bahkan ke disk). Jika Anda memutuskan untuk "me-restart" program dengan kelanjutan ini, maka status program Anda akan "diubah" ke keadaan pada saat objek kelanjutan diambil. Ini sama dengan beralih ke thread yang ditangguhkan, atau membangunkan OS setelah hibernasi. Kecuali Anda bisa melakukannya berkali-kali berturut-turut. Setelah OS aktif, informasi hibernasi dimusnahkan. Jika ini tidak dilakukan, maka status OS dapat dipulihkan dari titik yang sama. Ini hampir seperti perjalanan melintasi waktu. Dengan sekuel, Anda mampu membelinya!

    Dalam situasi apa kelanjutan akan bermanfaat? Biasanya jika Anda mencoba meniru negara dalam sistem yang pada dasarnya tidak memiliki negara. Penggunaan yang sangat baik untuk kelanjutan telah ditemukan dalam aplikasi Web (misalnya, dalam kerangka Seaside untuk bahasa Smalltalk). ASP.NET Microsoft berusaha keras untuk menyimpan status antar permintaan untuk membuat hidup Anda lebih mudah. Jika C# mendukung kelanjutan, kompleksitas ASP.NET dapat dikurangi setengahnya hanya dengan menyimpan kelanjutan dan memulihkannya pada permintaan berikutnya. Dari sudut pandang pemrogram Web, tidak akan ada satu pun jeda - program akan melanjutkan pekerjaannya dari baris berikutnya! Lanjutan adalah abstraksi yang sangat berguna untuk memecahkan beberapa masalah. Dengan semakin banyaknya klien gemuk tradisional yang berpindah ke Web, pentingnya kelanjutan akan semakin meningkat seiring berjalannya waktu.

    Pencocokan pola

    Pencocokan pola bukanlah ide baru atau inovatif. Faktanya, ini tidak ada hubungannya dengan pemrograman fungsional. Satu-satunya alasan sering dikaitkan dengan FP adalah karena untuk beberapa waktu sekarang bahasa fungsional memiliki pola yang cocok, tetapi bahasa imperatif tidak.

    Mari kita mulai pengenalan pencocokan Pola dengan contoh berikut. Berikut fungsi menghitung angka Fibonacci di Java:

    Int fib(int n) ( if(n == 0) kembalikan 1; if(n == 1) kembalikan 1; kembalikan fib(n - 2) + fib(n - 1); )
    Dan berikut adalah contoh dalam bahasa mirip Java dengan dukungan pencocokan Pola

    Int fib(0) ( kembalikan 1; ) int fib(1) ( kembalikan 1; ) int fib(int n) ( kembalikan fib(n - 2) + fib(n - 1); )
    Apa bedanya? Kompiler mengimplementasikan percabangan untuk kita.

    Bayangkan saja, ini sangat penting! Ini sebenarnya tidak terlalu penting. Telah diketahui bahwa sejumlah besar fungsi mengandung struktur sakelar yang kompleks (ini sebagian berlaku untuk program fungsional), dan diputuskan untuk menyoroti poin ini. Definisi fungsi dibagi menjadi beberapa varian, dan sebuah pola dibuat sebagai pengganti argumen fungsi (ini mengingatkan pada metode yang kelebihan beban). Ketika pemanggilan fungsi terjadi, kompilator membandingkan argumen dengan semua definisi dengan cepat dan memilih yang paling sesuai. Biasanya pilihan jatuh pada definisi fungsi yang paling terspesialisasi. Misalnya, int fib(int n) dapat dipanggil jika n bernilai 1, tetapi tidak, karena int fib(1) adalah definisi yang lebih khusus.

    Pencocokan pola biasanya terlihat lebih rumit daripada contoh kita. Misalnya sistem yang kompleks Pencocokan pola memungkinkan Anda menulis kode berikut:

    Ke dalam f(ke dalam n< 10) { ... } int f(int n) { ... }
    Kapan pencocokan pola berguna? Daftar kasus seperti ini ternyata sangat panjang! Setiap kali Anda menggunakan konstruksi if bersarang yang kompleks, pencocokan pola dapat bekerja lebih baik dengan lebih sedikit kode. Contoh bagus yang terlintas dalam pikiran adalah fungsi WndProc, yang diimplementasikan di setiap program Win32 (walaupun tersembunyi dari pemrogram di balik pagar abstraksi yang tinggi). Biasanya pencocokan pola bahkan dapat memeriksa isi koleksi. Misalnya, jika Anda meneruskan array ke suatu fungsi, maka Anda bisa memilih semua array yang elemen pertamanya sama dengan 1 dan elemen ketiganya lebih besar dari 3.

    Keuntungan lain dari pencocokan Pola adalah jika Anda membuat perubahan, Anda tidak perlu menggali satu fungsi besar pun. Yang perlu Anda lakukan hanyalah menambahkan (atau mengubah) beberapa definisi fungsi. Jadi, kita menyingkirkan seluruh lapisan pola dari buku terkenal Geng Empat. Semakin kompleks dan bercabang kondisinya, maka akan semakin berguna penggunaan pencocokan Pola. Begitu Anda mulai menggunakannya, Anda akan bertanya-tanya bagaimana Anda bisa berhasil tanpanya.

    Penutupan

    Sejauh ini, kita telah membahas ciri-ciri FP dalam konteks bahasa fungsional “murni” - bahasa yang merupakan implementasi kalkulus lambda dan tidak mengandung ciri-ciri yang bertentangan dengan sistem formal Gereja. Namun, banyak fitur bahasa fungsional yang digunakan di luar kalkulus lambda. Meskipun implementasi sistem aksiomatik menarik dari sudut pandang pemrograman dalam hal ekspresi matematika, hal ini mungkin tidak selalu dapat diterapkan dalam praktik. Banyak bahasa lebih suka menggunakan unsur bahasa fungsional tanpa menganut doktrin fungsional yang ketat. Beberapa bahasa seperti itu (misalnya Common Lisp) tidak memerlukan variabel final - nilainya dapat diubah. Mereka bahkan tidak mengharuskan fungsi untuk hanya bergantung pada argumennya—fungsi diperbolehkan mengakses status di luar cakupannya. Namun pada saat yang sama mereka menyertakan fitur-fitur seperti fungsi tingkat tinggi. Melewati fungsi dalam bahasa non-murni sedikit berbeda dari operasi yang sama dalam kalkulus lambda dan memerlukan kehadiran fitur menarik disebut: penutupan leksikal. Mari kita lihat contoh berikut. Ingatlah itu di dalam hal ini variabel bukanlah final dan suatu fungsi dapat mengakses variabel di luar cakupannya:

    Fungsi makePowerFn(int power) ( int powerFn(int base) ( return pow(base, power); ) return powerFn; ) Fungsi persegi = makePowerFn(2); kotak(3); // mengembalikan 9
    Fungsi make-power-fn mengembalikan fungsi yang mengambil satu argumen dan menaikkannya ke pangkat tertentu. Apa yang terjadi ketika kita mencoba mengevaluasi kuadrat(3) ? Variabel power berada di luar cakupan powerFn karena makePowerFn telah selesai dan tumpukannya telah dimusnahkan. Lalu bagaimana cara kerja persegi? Bahasa tersebut harus menyimpan makna kekuasaan agar fungsi kuadrat dapat berfungsi. Bagaimana jika kita membuat fungsi kubus lain yang menaikkan angka menjadi pangkat tiga? Bahasa tersebut harus menyimpan dua nilai daya untuk setiap fungsi yang dibuat di make-power-fn. Fenomena penyimpanan nilai-nilai ini disebut penutupan. Penutupan tidak hanya mempertahankan argumen dari fungsi teratas. Misalnya, penutupannya mungkin terlihat seperti ini:

    Fungsi makeInclinter() ( int n = 0; int inkremen() ( return ++n; ) ) Fungsi inc1 = makeInkrementer(); Fungsi inc2 = makeInkrementer(); inc1(); // mengembalikan 1; inc1(); // mengembalikan 2; inc1(); // mengembalikan 3; inc2(); // mengembalikan 1; inc2(); // mengembalikan 2; inc2(); // mengembalikan 3;
    Selama eksekusi, nilai n disimpan dan penghitung memiliki akses ke sana. Selain itu, setiap penghitung memiliki salinan nnya sendiri, meskipun faktanya penghitung tersebut seharusnya menghilang setelah fungsi makeIncrementer dijalankan. Bagaimana kompiler mengatur kompilasi ini? Apa yang terjadi di balik layar penutupan? Untungnya kami memiliki umpan ajaib.

    Semuanya dilakukan dengan cukup logis. Pada pandangan pertama, jelas bahwa variabel lokal tidak lagi tunduk pada aturan ruang lingkup dan masa pakainya tidak ditentukan. Jelas, mereka tidak lagi disimpan di tumpukan - mereka harus disimpan di tumpukan. Oleh karena itu penutupan dilakukan sebagai fungsi normal, yang telah kita bahas sebelumnya, hanya saja ia memiliki referensi tambahan ke variabel di sekitarnya:

    Kelas some_function_t (SimbolTable parentScope; // ... )
    Jika penutupan mengakses variabel yang tidak berada dalam cakupan lokal, maka cakupan induk akan diperhitungkan. Itu saja! Penutupan menghubungkan dunia fungsional dengan dunia OOP. Setiap kali Anda membuat kelas yang menyimpan beberapa status dan meneruskannya ke suatu tempat, ingatlah tentang penutupan. Penutupan hanyalah sebuah objek yang menciptakan "atribut" dengan cepat, mengeluarkannya dari cakupan sehingga Anda tidak perlu melakukannya sendiri.

    Bagaimana sekarang?

    Artikel ini hanya menyentuh puncak gunung es Pemrograman Fungsional. Anda dapat menggali lebih dalam dan melihat sesuatu yang sangat besar, dan dalam kasus kami, sesuatu yang bagus. Kedepannya saya berencana untuk menulis tentang teori kategori, monad, struktur fungsional data, sistem tipe dalam bahasa fungsional, multithreading fungsional, basis fungsional data, dan tentang banyak hal lainnya. Jika saya bisa menulis (dan mempelajarinya dalam prosesnya) bahkan tentang setengah dari topik-topik ini, hidup saya tidak akan sia-sia. Sementara itu, Google- teman setiamu.

    Jika Anda seorang pengembang seperti saya, Anda mungkin pertama kali mempelajari paradigma OOP. Bahasa pertama Anda adalah Java atau C++ - atau, jika beruntung, Ruby, Python, atau C# - jadi Anda mungkin tahu apa itu kelas, objek, instance, dll. Apa yang pasti tidak banyak Anda pahami adalah dasar-dasar paradigma aneh yang disebut pemrograman fungsional, yang berbeda secara signifikan tidak hanya dari OOP, tetapi juga dari pemrograman prosedural, berorientasi prototipe, dan jenis pemrograman lainnya.

    Pemrograman fungsional menjadi populer – dan untuk alasan yang bagus. Paradigma ini sendiri bukanlah hal baru: Haskell mungkin merupakan bahasa yang paling fungsional, dan muncul pada tahun 90an. Bahasa seperti Erlang, Scala, Clojure juga termasuk dalam definisi fungsional. Salah satu keuntungan utama pemrograman fungsional adalah kemampuan untuk menulis program yang bekerja secara bersamaan (jika Anda lupa apa ini, segarkan ingatan Anda dengan membaca), dan tanpa kesalahan - yaitu, saling mengunci dan keamanan thread tidak akan mengganggu Anda .

    Pemrograman fungsional memiliki banyak keuntungan, namun kemampuan untuk memaksimalkan sumber daya CPU melalui perilaku bersamaan adalah keuntungan utamanya. Di bawah ini kita akan melihat prinsip dasar pemrograman fungsional.

    Perkenalan: Semua prinsip ini bersifat opsional (banyak bahasa tidak sepenuhnya mengikutinya). Semuanya bersifat teoretis dan paling dibutuhkan definisi yang tepat paradigma fungsional.

    1. Semua fungsi murni

    Aturan ini tentunya sangat mendasar dalam pemrograman fungsional. Semua fungsi dikatakan murni jika memenuhi dua kondisi:

    1. Fungsi yang dipanggil dengan argumen yang sama selalu mengembalikan nilai yang sama.
    2. Tidak ada efek samping yang terjadi selama pelaksanaan fungsi.

    Aturan pertama jelas - jika saya memanggil fungsi sum(2, 3), saya berharap hasilnya selalu 5. Segera setelah Anda memanggil fungsi rand(), atau mengakses variabel yang tidak ditentukan dalam fungsi tersebut, kemurnian fungsi dilanggar, dan ini tidak diperbolehkan dalam pemrograman fungsional.

    Aturan kedua - tidak ada efek samping - lebih luas sifatnya. Efek samping adalah perubahan pada sesuatu selain fungsi yang sedang dijalankan. Mengubah variabel di luar fungsi, mencetak ke konsol, melemparkan pengecualian, membaca data dari file - semua ini adalah contoh efek samping yang menghilangkan kemurnian fungsi. Ini mungkin tampak seperti batasan besar, tapi pikirkan lagi. Jika Anda yakin bahwa pemanggilan suatu fungsi tidak akan mengubah apa pun "di luar", Anda dapat menggunakan fungsi ini dalam skenario apa pun. Ini membuka jalan untuk pemrograman bersamaan dan aplikasi multi-thread.

    2. Semua fungsi adalah kelas satu dan tingkat yang lebih tinggi

    Konsep ini bukan merupakan fitur FP (digunakan dalam Javascript, PHP, dan bahasa lainnya) - tetapi merupakan persyaratan wajib. Faktanya, Wikipedia memiliki seluruh artikel yang didedikasikan untuk fungsi kelas satu. Agar suatu fungsi menjadi kelas satu, ia harus dapat dideklarasikan sebagai variabel. Hal ini memungkinkan fungsi diperlakukan sebagai tipe data normal dan tetap dijalankan.

    3. Variabel tidak dapat diubah

    Semuanya sederhana di sini. Dalam pemrograman fungsional, Anda tidak dapat mengubah variabel setelah diinisialisasi. Anda dapat membuat yang baru, tetapi Anda tidak dapat mengubah yang sudah ada - dan berkat ini, Anda dapat yakin bahwa tidak ada variabel yang akan berubah.

    4. Transparansi fungsi yang relatif

    Sulit untuk memberikan definisi yang tepat mengenai transparansi relatif. Saya pikir yang paling akurat adalah ini: jika Anda dapat mengganti pemanggilan fungsi dengan nilai kembalian, dan statusnya tidak berubah, maka fungsinya relatif transparan. Ini mungkin sudah jelas, tapi saya akan memberikan contoh.

    Katakanlah kita memiliki fungsi Java yang menambahkan 3 dan 5:

    int publik addNumbers() ( kembalikan 3 + 5; ) addNumbers() // 8 8 // 8

    Jelasnya, setiap panggilan ke fungsi ini dapat diganti dengan 8 - yang berarti fungsi tersebut relatif transparan. Berikut adalah contoh fungsi buram:

    Public void printText())( System.out.println("Hello World"); ) printText() // Tidak mengembalikan apa pun, namun mencetak "Hello World"

    Fungsi ini tidak mengembalikan apa pun, tetapi mencetak teks, dan jika Anda mengganti pemanggilan fungsi dengan apa pun, status konsol akan berbeda - yang berarti fungsi tersebut relatif tidak transparan.

    5. Pemrograman fungsional didasarkan pada kalkulus lambda

    Pemrograman fungsional sangat bergantung pada sistem matematika yang disebut kalkulus lambda. Saya bukan ahli matematika, jadi saya tidak akan menjelaskan secara detail - tetapi saya ingin menarik perhatian pada dua prinsip utama kalkulus lambda yang membentuk konsep pemrograman fungsional:

    1. Dalam kalkulus lambda, semua fungsi bisa anonim karena satu-satunya bagian yang berarti dari header fungsi adalah daftar argumen.
    2. Saat dipanggil, semua fungsi melalui proses kari. Caranya sebagai berikut: jika suatu fungsi dengan beberapa argumen dipanggil, maka mula-mula fungsi tersebut akan dieksekusi hanya dengan argumen pertama dan akan mengembalikan fungsi baru yang berisi 1 argumen lebih sedikit, yang akan segera dipanggil. Proses ini bersifat rekursif dan berlanjut hingga semua argumen diterapkan, dan mengembalikan hasil akhir. Karena fungsinya murni, ini berfungsi.

    Seperti yang saya katakan, kalkulus lambda tidak berakhir di situ - tetapi kami hanya membahas aspek-aspek utama yang terkait dengan FP. Sekarang, dalam percakapan tentang pemrograman fungsional, Anda dapat membuang kata "kalkulus lambda" dan semua orang akan mengira Anda sedang main-main :)

    • Sergei Savenkov

      semacam ulasan "pendek"... seolah-olah mereka sedang terburu-buru di suatu tempat