Apa warna fungsi Anda?

Saya tidak tahu tentang Anda, tetapi bagi saya tidak ada yang lebih baik untuk memulai hari daripada memikirkan pemrograman. Darah mendidih saat melihat kritik yang sukses terhadap salah satu bahasa "berani" yang digunakan oleh orang-orang Plebeian, tersiksa dengan itu sepanjang hari kerja antara kunjungan yang pemalu ke StackOverflow.


(Sementara itu, Anda dan saya hanya menggunakan bahasa yang paling tercerahkan dan alat-alat canggih yang dirancang untuk tangan-tangan ahli seperti kita).


Tentu saja, sebagai penulis khotbah, saya mengambil risiko. Anda mungkin menyukai bahasa yang saya hina! Sebuah pamflet yang ceroboh mungkin secara tidak sengaja membawa ke blog saya gerombolan ponsel yang marah dengan garpu rumput dan obor.


Untuk melindungi diri saya dari api yang benar dan tidak menyinggung perasaan Anda (mungkin halus), saya akan berbicara tentang bahasa ...


... yang baru saja datang. Tentang patung jerami, yang perannya hanya membakar kritik di tiang pancang.


Saya tahu ini kedengarannya konyol, tapi percayalah, pada akhirnya kita akan melihat wajah siapa (atau wajah) yang dilukis di atas kepala jerami.


Bahasa baru


Ini akan menjadi infleksi untuk mempelajari bahasa yang sama sekali baru (dan sial) hanya untuk artikel blog, jadi katakanlah itu sangat mirip dengan bahasa yang sudah kita ketahui. Misalnya Javascript. Kurung kurawal dan titik koma. if , while , dll. - Lingua franca dari kerumunan kita.


Saya memilih JS bukan karena artikel ini tentang dia. Ini hanya bahasa yang bisa dipahami oleh pembaca rata-rata. Voila:


 function thisIsAFunction(){ return "!"; } 

Karena boneka kami adalah bahasa yang keren (baca - buruk), ia memiliki fungsi kelas satu . Jadi Anda dapat menulis sesuatu seperti ini:


 //  ,     , //    function filter(collection, predicate) { var result = []; for (var i = 0; i < collection.length; i++) { if (predicate(collection[i])){ result.push(collection[i]); } } return result; } 

Ini adalah salah satu fitur kelas satu , dan seperti namanya, mereka keren dan sangat berguna. Anda mungkin terbiasa mengubah koleksi data dengan bantuan mereka, tetapi begitu Anda memahami konsepnya, Anda mulai menggunakannya di mana-mana, sial.


Mungkin dalam tes:


 describe("", function(){ it(" ", function(){ expect("").not.toBe(""); }); }; 

Atau saat Anda perlu mengurai (parse) data:


 tokens.match(Token.LEFT_BRACKET, function(token){ // Parse a list literal... tokens.consume(Token.RIGHT_BRACKET); }); 

Kemudian, setelah berakselerasi, Anda menulis semua jenis pustaka yang dapat digunakan kembali dan aplikasi yang berputar di sekitar fungsi, pemanggilan fungsi, pengembalian fungsi dari fungsi - bilik fungsional.


penerjemah: dalam bahasa asli "Functapalooza". Awalan -a-palooza sangat keren sehingga Anda ingin membaginya dengan semua orang.

Apa warna fungsi Anda?


Dan di sini keanehan dimulai. Bahasa kami memiliki satu fitur khusus:


1. Setiap fungsi memiliki warna.


Setiap fungsi - panggilan balik anonim atau fungsi biasa dengan nama - berwarna merah atau biru. Karena penyorotan kode di blog kami tidak menyoroti warna fungsi yang berbeda, mari kita sepakat bahwa sintaksnya adalah:


 blue*function doSomethingAzure(){ //   ... } red*function doSomethingCarnelian(){ //    ... } 

Bahasa kami tidak memiliki fungsi tidak berwarna. Ingin membuat fitur? - harus memilih warna. Ini peraturannya. Dan ada beberapa aturan lagi yang harus Anda ikuti:


2. Warna mempengaruhi cara fungsi dipanggil


Bayangkan ada dua sintaks untuk fungsi memanggil - "biru" dan "merah". Sesuatu seperti:


 doSomethingAzure(...)*blue; doSomethingCarnelian()*red; 

Saat Anda memanggil suatu fungsi, Anda harus menggunakan panggilan yang cocok dengan warnanya. Jika Anda tidak menebak - mereka menyebut fungsi merah dengan *blue setelah tanda kurung (atau sebaliknya) - sesuatu yang sangat buruk akan terjadi. Mimpi buruk masa kecil yang telah lama terlupakan, seperti badut dengan ular, bukannya tangan yang bersembunyi di bawah tempat tidur Anda. Dia akan melompat keluar dari monitor dan menyedot mata Anda.


Aturan bodoh, kan? Oh, tetapi satu hal lagi:


3. Hanya fungsi merah yang dapat menyebabkan fungsi merah.


Anda dapat memanggil fungsi biru dari merah. Ini halal:


 red*function doSomethingCarnelian(){ doSomethingAzure()*blue; } 

Tapi tidak sebaliknya. Jika Anda mencoba:


 blue*function doSomethingAzure(){ doSomethingCarnelian()*red; } 

- Anda akan dikunjungi oleh Clown Spider Maw tua.


Ini membuatnya lebih sulit untuk menulis fungsi yang lebih tinggi seperti filter() dari contoh. Kita harus memilih warna untuk setiap fungsi baru dan ini mempengaruhi warna fungsi yang bisa kita lewati. Solusi yang jelas adalah membuat filter() merah. Maka kita bisa memanggil setidaknya fungsi merah, setidaknya biru. Tapi kemudian kita terluka tentang duri berikutnya di mahkota duri, yang merupakan bahasa yang diberikan:


4. Fungsi merah menyebabkan rasa sakit


Kami tidak akan menunjukkan "rasa sakit" ini, bayangkan saja programmer harus melompat melalui lingkaran setiap kali ia memanggil fungsi merah. Panggilan mungkin terlalu polisilabik atau Anda tidak dapat menjalankan fungsi di dalam beberapa ekspresi. Atau Anda hanya dapat mengakses fungsi merah dari garis ganjil.


Tidak masalah apa itu, tetapi jika Anda memutuskan untuk membuat fungsinya merah, semua orang yang menggunakan API Anda ingin meludahi kopi Anda atau melakukan sesuatu yang lebih buruk.


Solusi yang jelas dalam hal ini adalah tidak pernah menggunakan fungsi merah. Jadikan semuanya biru, dan Anda kembali ke dunia normal, di mana semua fungsi memiliki warna yang sama, yang sama dengan fakta bahwa mereka tidak memiliki warna dan bahasa kami tidak sepenuhnya bodoh.


Sayangnya, para sadis yang mengembangkan bahasa ini (semua orang tahu bahwa penulis bahasa pemrograman sadis, kan?) Tempelkan duri terakhir dalam diri kita:


5. Beberapa fungsi inti bahasa adalah merah.


Beberapa fungsi dibangun ke dalam platform, fungsi yang perlu kita gunakan yang tidak dapat ditulis sendiri, hanya tersedia dalam warna merah. Pada titik ini, orang yang cerdas mungkin mulai curiga bahwa bahasa ini membenci kita.


Ini semua kesalahan bahasa fungsional!


Anda mungkin berpikir bahwa masalahnya adalah kami mencoba menggunakan fungsi tingkat tinggi. Jika kita berhenti bermain-main dengan semua omong kosong fungsional ini, dan mulai menulis fungsi urutan pertama biru normal (fungsi yang tidak beroperasi dengan fungsi lain - kira-kira Penerjemah), seperti yang direncanakan oleh Tuhan - kita akan menghilangkan semua rasa sakit ini.


Jika kita hanya memanggil fungsi biru, kita membuat semua fungsi kita biru. Kalau tidak, kita membuat merah. Sampai kita membuat fungsi yang menerima fungsi, kita tidak perlu khawatir tentang "polimorfisme dengan warna fungsi" (polikromatik?) Atau omong kosong lainnya.


Namun sayang, fungsi tingkat tinggi hanyalah satu contoh. Masalah muncul setiap kali kita ingin memecah program kita menjadi fungsi untuk digunakan kembali.


Sebagai contoh, kami memiliki sepotong kode kecil yang, yah, saya tidak tahu, mengimplementasikan algoritma Dijkstra pada grafik yang menunjukkan seberapa banyak koneksi sosial Anda menekan satu sama lain. (Saya menghabiskan banyak waktu untuk memutuskan apa artinya hasilnya. Transitif tidak diinginkan?)


Kemudian Anda perlu menggunakan algoritma ini di tempat lain. Secara alami, Anda membungkus kode dalam fungsi terpisah. Panggil dia dari tempat lama dan dari tempat baru. Tapi apa warna fungsinya? Anda mungkin akan mencoba membuatnya menjadi biru, tetapi bagaimana jika ia menggunakan salah satu dari fungsi "hanya merah" yang jahat ini dari pustaka kernel?


Katakanlah tempat baru tempat Anda ingin memanggil fungsi berwarna biru? Tetapi sekarang Anda perlu menulis ulang kode panggilan dengan warna merah. Dan kemudian ulangi fungsi yang memanggil kode ini. Fiuh Anda harus selalu mengingat warnanya. Ini akan menjadi pasir di celana renang Anda di pemrograman liburan pantai.


Alegori warna


Sebenarnya, saya tidak berbicara tentang warna. Ini adalah alegori, alat sastra. Persetan - ini bukan tentang bintang - bintang di perut , ini tentang perlombaan. Anda mungkin sudah curiga ...


Fungsi Merah - Asinkron


Jika Anda memprogram dalam JavaScript atau Node.js, setiap kali Anda menentukan fungsi yang memanggil fungsi panggilan balik (callback) untuk "mengembalikan" hasilnya, Anda menulis fungsi merah. Lihatlah daftar aturan ini dan perhatikan bagaimana hal itu sesuai dengan metafora saya:


  1. Fungsi sinkron mengembalikan hasil, fungsi asinkron tidak, sebaliknya, mereka memanggil panggilan balik.
  2. Fungsi sinkron mengembalikan hasilnya sebagai nilai balik, fungsi asinkron mengembalikannya, menyebabkan panggilan balik yang Anda berikan kepada mereka.
  3. Anda tidak dapat memanggil fungsi asinkron dari yang sinkron, karena Anda tidak dapat mengetahui hasilnya sampai fungsi asinkron dijalankan kemudian.
  4. Fungsi asinkron tidak dikompilasi ke dalam ekspresi karena panggilan balik, memerlukan kesalahan mereka untuk ditangani secara berbeda dan tidak dapat digunakan dalam blok try/catch atau dalam sejumlah ekspresi lain yang mengontrol program.
  5. semuanya tentang Node.js adalah bahwa pustaka kernel semuanya asinkron. (Meskipun mereka menyumbang kembali dan mulai menambahkan versi _Sync() ke banyak hal.)

Ketika orang berbicara tentang "panggilan balik neraka" , mereka berbicara tentang betapa menjengkelkannya memiliki fungsi "merah" dalam bahasa mereka. Ketika mereka membuat 4089 perpustakaan untuk pemrograman asinkron (pada 2019 sudah 11217 - approx. Translator), mereka mencoba untuk mengatasi masalah di tingkat perpustakaan bahwa mereka telah terjebak dengan bahasa.


Saya berjanji masa depan lebih baik


dalam terjemahan: "Saya berjanji bahwa masa depan lebih baik" permainan kata-kata dari judul dan isi bagian tersebut hilang

Orang-orang di Node.js telah lama menyadari bahwa callback sakit, dan sedang mencari solusi. Salah satu teknik yang telah menginspirasi banyak orang adalah promises , yang mungkin juga Anda kenal dengan nama panggilan futures .


dalam IT Rusia, alih-alih menerjemahkan "janji" sebagai "janji", menelusuri kertas dari bahasa Inggris - "janji" dibuat. Kata "Futures" digunakan sebagaimana adanya, mungkin karena "futures" sudah ditempati oleh gaul keuangan.

Promis adalah pembungkus untuk penelepon balik dan penangan kesalahan. Jika Anda berpikir untuk meneruskan panggilan balik untuk hasil dan panggilan balik lain untuk kesalahan, maka future adalah perwujudan dari ide ini. Ini adalah objek dasar yang merupakan operasi asinkron.


Saya baru saja mendapat banyak kata-kata indah dan mungkin terdengar seperti solusi yang bagus, tetapi kebanyakan itu adalah minyak ular . Janji benar-benar membuat penulisan kode asinkron sedikit lebih mudah. Mereka lebih mudah untuk dikomposisi menjadi ekspresi, jadi aturan 4 sedikit kurang ketat.


Tapi jujur ​​saja, itu seperti perbedaan antara pukulan di perut atau pangkal paha. Ya, itu tidak terlalu menyakitkan, tetapi tidak ada yang akan senang dengan pilihan seperti itu.


Anda masih tidak dapat menggunakan janji dengan penanganan pengecualian atau lainnya
mengelola operator. Anda tidak dapat memanggil fungsi yang mengembalikan future dari kode sinkron. (Anda bisa , tetapi kemudian pemelihara kode Anda berikutnya akan menciptakan mesin waktu, kembali pada saat Anda melakukannya, dan menempelkan pensil di wajah Anda karena alasan # 2.)


Janji-janji masih membagi dunia Anda menjadi dua bagian yang tidak sinkron dan sinkron dengan semua penderitaan berikutnya. Jadi, bahkan jika bahasa Anda mendukung promises atau futures , itu masih sangat mirip boneka saya.


(Ya, ini bahkan termasuk Dart yang saya gunakan. Oleh karena itu, saya sangat senang bahwa bagian dari tim sedang mencoba pendekatan lain untuk paralelisme )


tautan proyek secara resmi ditinggalkan

Saya sedang menunggu solusi


Pemrogram C # mungkin merasa sombong (alasan mereka menjadi semakin banyak korban adalah karena Halesberg dan perusahaan menaburkan semuanya dan menaburkan bahasa dengan gula sintaksis). Di C #, Anda dapat menggunakan kata kunci await untuk memanggil fungsi asinkron.


Ini membuat panggilan asinkronik semudah sinkron, dengan penambahan kata kunci kecil yang lucu. Anda dapat memasukkan panggilan await dalam ekspresi, menggunakannya dalam penanganan pengecualian, dalam aliran instruksi. Anda bisa menjadi gila. Biarkan hujan menunggu seperti uang untuk album rapper baru Anda.


Async-waiting menunggu, jadi kami menambahkannya ke Dart. Jauh lebih mudah untuk menulis kode asinkron dengannya. Tapi, seperti biasa, ada satu "Tapi." Ini dia. Tapi ... Anda masih membagi dunia menjadi dua. Fungsi asinkron sekarang lebih mudah untuk ditulis, tetapi mereka tetap fungsi asinkron.


Anda masih memiliki dua warna. Async-menunggu menunggu masalah yang mengganggu # 4 - mereka membuat memanggil fungsi merah tidak lebih sulit daripada memanggil yang biru. Tetapi sisa peraturan masih ada di sini:


  1. Fungsi sinkron mengembalikan nilai, fungsi asinkron mengembalikan pembungkus ( Task<T> dalam C # atau Future<T> di Dart) di sekitar nilai.
  2. Sinkron baru saja dipanggil, asinkron perlu await .
  3. Dengan memanggil fungsi asinkron, Anda mendapatkan objek wrapper ketika Anda benar-benar menginginkan suatu nilai. Anda tidak dapat memperluas nilai sampai Anda membuat fungsi Anda tidak sinkron dan menyebutnya dengan await (tetapi lihat paragraf berikutnya).
  4. Selain sedikit menunggu hiasan, setidaknya kami menyelesaikan masalah ini.
  5. Pustaka inti C # lebih tua dari asinkron, jadi saya pikir mereka tidak pernah mengalami masalah ini.

Async benar Async benar lebih baik. Saya lebih suka async-menunggu panggilan balik telanjang setiap hari dalam seminggu. Tetapi kita membohongi diri kita sendiri jika kita berpikir bahwa semua masalah diselesaikan. Segera setelah Anda mulai menulis fungsi tingkat tinggi, atau menggunakan kembali kode, Anda kembali menyadari bahwa warnanya masih ada, membanjiri semua kode sumber Anda.


Bahasa mana yang bukan warna?


Jadi JS, Dart, C # dan Python memiliki masalah ini. CoffeeScript dan sebagian besar bahasa lain yang dikompilasi di JS juga (dan Dart diwarisi). Saya pikir bahkan ClojureScript memiliki tangkapan ini, meskipun upaya aktif mereka dengan core.async


Ingin tahu yang mana yang tidak? Jawa Apakah saya benar Seberapa sering Anda berkata, “Ya, Jawa sendiri yang melakukannya dengan benar”? Dan begitulah yang terjadi. Dalam pembelaan mereka, mereka secara aktif berusaha memperbaiki pengawasan mereka dengan mempromosikan IO futures dan async. Ini seperti balapan yang lebih buruk dari yang lebih buruk.


semuanya sudah ada di Jawa

C #, pada kenyataannya, juga bisa mengatasi masalah ini. Mereka memilih memiliki warna. Sebelum mereka menambahkan async-waiting dan semua Task<T> ini Task<T> sampah, Anda bisa menggunakan panggilan API sinkron biasa. Tiga bahasa lain yang tidak memiliki masalah "warna": Go, Lua, dan Ruby.


Tebak kesamaan apa yang mereka miliki?


Streaming. Atau lebih tepatnya: banyak tumpukan panggilan independen yang dapat beralih . Ini belum tentu utas sistem operasi. Coroutine in Go, coroutine in Lua, dan utas di Ruby semuanya memadai.


(Itulah mengapa ada peringatan kecil untuk C # - Anda dapat menghindari rasa sakit yang tidak sinkron dalam C # dengan menggunakan utas.)


Memori operasi sebelumnya


Masalah mendasarnya adalah "bagaimana melanjutkan dari tempat yang sama ketika operasi (asinkron) selesai"? Anda terjun ke jurang tumpukan panggilan dan kemudian memanggil semacam operasi I / O. Demi akselerasi, operasi ini menggunakan API asinkron yang mendasari OS Anda. Anda tidak bisa menunggu sampai selesai. Anda harus kembali ke loop acara bahasa Anda dan memberikan waktu OS untuk menyelesaikan operasi.


Setelah ini terjadi, Anda perlu melanjutkan apa yang Anda lakukan. Biasanya bahasa "mengingat tempatnya" melalui tumpukan panggilan . Dia mengikuti semua fungsi yang telah dipanggil saat ini, dan melihat di mana counter perintah di masing-masing menunjukkan.


Tetapi untuk melakukan I / O asinkron, Anda harus melepas, membuang seluruh tumpukan panggilan dalam C. Ketik Trick-22. Anda memiliki I / O yang sangat cepat, tetapi Anda tidak dapat menggunakan hasilnya! Semua bahasa dengan I / O asinkron di bawah tenda - atau, dalam kasus JS, loop acara browser - dipaksa untuk menangani hal ini.


Node, dengan panggilan balik yang tepat untuk selamanya, memasukkan semua panggilan ini ke dalam penutupan. Ketika Anda menulis:


 function makeSundae(callback) { scoopIceCream(function (iceCream) { warmUpCaramel(function (caramel) { callback(pourOnIceCream(iceCream, caramel)); }); }); } 

Masing-masing ekspresi fungsional ini menutup seluruh konteks sekitarnya. Ini mentransfer parameter, seperti iceCream dan caramel , dari tumpukan panggilan ke tumpukan . Ketika fungsi eksternal mengembalikan hasil dan tumpukan panggilan dihancurkan, itu keren. Data masih berada di suatu tempat di heap.


Masalahnya adalah bahwa Anda harus menghidupkan kembali masing-masing panggilan sialan ini lagi. Bahkan ada nama khusus untuk konversi ini: gaya kelanjutan-kelanjutan


tautkan fungsionalitas yang ganas

Ini ditemukan oleh peretas bahasa di tahun 70-an, sebagai representasi perantara untuk digunakan di bawah kap kompiler. Ini adalah cara yang sangat aneh untuk memperkenalkan kode yang membuatnya lebih mudah untuk melakukan beberapa optimasi kompiler.


Tidak ada yang pernah berpikir bahwa seorang programmer dapat menulis kode seperti itu . Dan kemudian Node muncul, dan tiba-tiba kita semua berpura-pura menulis backend kompiler. Di mana kita berbelok ke arah yang salah?


Perhatikan bahwa janji dan futures tidak banyak membantu. Jika Anda menggunakannya, Anda tahu bahwa Anda masih menumpuk lapisan ekspresi fungsional raksasa. Anda hanya meneruskannya ke .then() alih-alih fungsi asinkron itu sendiri.


Menunggu solusi yang dihasilkan


Async-waiting sangat membantu. Jika Anda melihat di bawah kap ke kompiler ketika bertemu await , Anda akan melihat bahwa itu benar-benar melakukan konversi CPS. Itu sebabnya Anda perlu menggunakan await di C # - ini adalah petunjuk untuk kompiler - "hentikan fungsi di tengah sini." Semuanya setelah await menjadi fungsi baru yang disatukan oleh kompiler atas nama Anda.


Inilah sebabnya mengapa async-wait tidak membutuhkan dukungan runtime di dalam .NET framework. Kompiler mengkompilasi ini ke dalam sebuah rantai penutupan terkait, yang sudah diketahui bagaimana cara menangani. (Menariknya, penutupan juga tidak memerlukan dukungan runtime. Mereka dikompilasi ke dalam kelas anonim. Dalam C #, penutupan hanyalah objek.)


Anda mungkin bertanya-tanya ketika saya menyebutkan generator. Apakah ada yield dalam bahasa Anda? Lalu dia bisa melakukan sesuatu yang sangat mirip.


(Saya percaya bahwa generator dan async-menunggu sebenarnya isomorfik. Di suatu tempat di celah-celah dan celah-celah hard drive saya terletak sepotong kode yang mengimplementasikan loop permainan pada generator hanya menggunakan async-tunggu).


Jadi dimana saya? Oh ya Jadi dengan callback, janji, menunggu async dan generator, Anda akhirnya mengambil fungsi asinkron Anda dan memecahnya menjadi banyak penutupan yang hidup di heap.


Fungsi Anda memanggil eksternal saat runtime. Ketika loop acara atau operasi I / O selesai, fungsi Anda dipanggil dan berlanjut dari tempat sebelumnya. Tetapi ini berarti semua yang ada di atas fungsi Anda juga harus kembali. Anda masih perlu mengembalikan seluruh tumpukan.


Di sinilah aturan berasal: "Anda dapat memanggil fungsi merah hanya dari fungsi merah". Anda harus menyimpan seluruh tumpukan panggilan di closure ke main() atau event handler.


Panggil implementasi stack


( ), . .


Go, , . -, Go , -.


- Golang, . , , . , Javascript. Go- , IO . Go .


Go — , . , , .


, API, , . .





, , . , . , 50% .


, , , .


Javascript -, , , JS , JS , . , JS .


, ( ) — , , , async . import threading ( , AsyncIO, Twisted Tornado, ).


, , , , , , .


, Go, Go .


, , , ( - ) , "async-await ". .


, .


, , .

Source: https://habr.com/ru/post/id466337/


All Articles