OK, jadi judulnya sedikit clickbaity tapi serius saya sudah tahu, jangan minta tendangan untuk sementara waktu. Saya suka bagaimana ini mendorong metode untuk digunakan sebagai pesan dalam mode berorientasi objek yang benar. Tetapi ini memiliki masalah yang mengganggu yang telah mengoceh di kepala saya.
Saya curiga bahwa kode yang ditulis dengan baik dapat mengikuti prinsip-prinsip OO dan prinsip-prinsip fungsional secara bersamaan. Saya mencoba mendamaikan ide-ide ini dan poin penting yang saya dapatkan adalah return
.
Fungsi murni memiliki dua kualitas:
Menyebutnya berulang kali dengan input yang sama selalu memberikan hasil yang sama. Ini menyiratkan bahwa itu tidak berubah. Keadaannya diatur hanya sekali.
Tidak menghasilkan efek samping. Satu-satunya perubahan yang disebabkan oleh panggilan itu adalah menghasilkan hasilnya
Jadi, bagaimana seseorang berfungsi murni jika Anda telah bersumpah menggunakan return
cara Anda mengkomunikasikan hasil?
The kirim, jangan tanya karya ide dengan menggunakan apa yang beberapa akan mempertimbangkan efek samping. Ketika saya berurusan dengan suatu objek saya tidak menanyakannya tentang keadaan internalnya. Saya katakan apa yang harus saya lakukan dan menggunakan keadaan internal untuk mencari tahu apa yang harus dilakukan dengan apa yang saya perintahkan. Setelah saya katakan, saya tidak bertanya apa yang dilakukannya. Saya hanya berharap itu telah melakukan sesuatu tentang apa yang diperintahkan untuk dilakukan.
Saya memikirkan Tell, Don't Ask sebagai lebih dari sekedar nama yang berbeda untuk enkapsulasi. Ketika saya menggunakan return
saya tidak tahu apa yang memanggil saya. Saya tidak dapat berbicara protokol itu, saya harus memaksanya untuk berurusan dengan protokol saya. Yang dalam banyak kasus dinyatakan sebagai keadaan internal. Bahkan jika apa yang diekspos tidak benar-benar keadaan itu biasanya hanya beberapa perhitungan dilakukan pada keadaan dan masukan argumen. Memiliki antarmuka untuk merespons melalui pemberian kesempatan untuk memijat hasil menjadi sesuatu yang lebih bermakna daripada keadaan internal atau perhitungan. Itu adalah pesan yang lewat . Lihat contoh ini .
Kembali ke masa lalu, ketika disk drive benar-benar memiliki disk di dalamnya dan thumb drive adalah apa yang Anda lakukan di dalam mobil ketika roda terlalu dingin untuk disentuh dengan jari-jari Anda, saya diajari bagaimana orang yang menjengkelkan mempertimbangkan fungsi yang memiliki parameter. void swap(int *first, int *second)
tampak sangat berguna tetapi kami didorong untuk menulis fungsi yang mengembalikan hasilnya. Jadi saya menaruh hati ini pada iman dan mulai mengikutinya.
Tapi sekarang saya melihat orang-orang membangun arsitektur di mana objek membiarkan bagaimana mereka dibangun mengendalikan ke mana mereka mengirim hasilnya. Inilah contoh implementasi . Menyuntikkan objek port keluaran sepertinya sedikit seperti ide parameter keluar lagi. Tapi begitulah cara mengatakan-jangan-tanya benda memberi tahu objek lain apa yang telah mereka lakukan.
Ketika saya pertama kali belajar tentang efek samping saya menganggapnya seperti parameter output. Kami diberitahu untuk tidak mengejutkan orang-orang dengan membuat beberapa pekerjaan terjadi dengan cara yang mengejutkan, yaitu dengan tidak mengikuti return result
konvensi. Sekarang tentu, saya tahu ada tumpukan masalah threading asynchronous paralel yang efek samping bercampur dengan tetapi kembali benar-benar hanya sebuah konvensi yang membiarkan Anda mendorong hasilnya pada tumpukan sehingga apa pun yang disebut Anda dapat mematikannya nanti. Hanya itu yang sebenarnya.
Apa yang sebenarnya ingin saya tanyakan:
Apakah return
satu-satunya cara untuk menghindari semua efek samping kesengsaraan dan mendapatkan keselamatan benang tanpa kunci, dll. Atau bisakah saya mengikuti ceritanya, jangan bertanya dengan cara yang murni fungsional?
sumber
Jawaban:
Jika suatu fungsi tidak memiliki efek samping dan tidak mengembalikan apa pun, maka fungsi tersebut tidak berguna. Sesederhana itu.
Tapi saya kira Anda bisa menggunakan beberapa cheat jika Anda ingin mengikuti surat aturan dan mengabaikan alasan yang mendasarinya. Misalnya menggunakan parameter keluar secara tegas tidak menggunakan return. Tetapi masih tetap sama persis dengan pengembalian, hanya dengan cara yang lebih berbelit-belit. Jadi, jika Anda yakin pengembalian buruk karena suatu alasan , maka menggunakan parameter keluar jelas buruk karena alasan mendasar yang sama.
Anda dapat menggunakan lebih banyak cheat yang berbelit-belit. Misalnya Haskell terkenal dengan trik IO monad di mana Anda dapat memiliki efek samping dalam praktek, tetapi masih belum sepenuhnya memiliki efek samping dari sudut pandang teoritis. Gaya kelanjutan-lewat adalah trik lain, yang memungkinkan Anda menghindari pengembalian dengan mengubah kode Anda menjadi spageti.
Intinya adalah, tidak ada trik konyol, dua prinsip fungsi bebas efek samping dan "tidak ada pengembalian" sama sekali tidak kompatibel. Lebih lanjut saya akan menunjukkan keduanya adalah prinsip yang benar-benar buruk (dogma benar-benar) pada awalnya, tetapi itu adalah diskusi yang berbeda.
Aturan seperti "katakan, jangan tanya" atau "tidak ada efek samping" tidak dapat diterapkan secara universal. Anda selalu harus mempertimbangkan konteksnya. Program tanpa efek samping benar-benar tidak berguna. Bahkan bahasa fungsional murni pun mengakui hal itu. Sebaliknya mereka berusaha untuk memisahkan bagian murni dari kode dari yang dengan efek samping. Titik State atau IO monads di Haskell bukanlah bahwa Anda menghindari efek samping - karena Anda tidak bisa - tetapi keberadaan efek samping secara eksplisit ditunjukkan oleh tanda tangan fungsi.
Aturan tell-dont-ask berlaku untuk jenis arsitektur yang berbeda - gaya di mana objek dalam program adalah "aktor" independen yang berkomunikasi satu sama lain. Setiap aktor pada dasarnya otonom dan terbungkus. Anda dapat mengirimnya pesan dan memutuskan bagaimana bereaksi terhadapnya, tetapi Anda tidak dapat memeriksa keadaan internal aktor dari luar. Ini berarti Anda tidak dapat memberi tahu apakah sebuah pesan mengubah keadaan internal aktor / objek. Keadaan dan efek samping disembunyikan oleh desain .
sumber
Katakan, Don't Ask disertai dengan beberapa asumsi mendasar:
Tidak satu pun dari hal-hal ini berlaku untuk fungsi murni.
Jadi mari kita tinjau mengapa kita memiliki aturan "Katakan, Jangan Tanyakan." Aturan ini adalah peringatan dan pengingat. Dapat diringkas seperti ini:
Dengan kata lain, kelas semata-mata bertanggung jawab untuk mempertahankan negara mereka sendiri dan menindaklanjutinya. Ini adalah tentang enkapsulasi.
Dari Fowler :
Untuk menegaskan kembali, semua ini tidak ada hubungannya dengan fungsi murni, atau bahkan yang tidak murni kecuali Anda mengekspos keadaan kelas ke dunia luar. Contoh:
Pelanggaran TDA
Bukan Pelanggaran TDA
atau
Dalam kedua contoh terakhir, lampu lalu lintas mempertahankan kontrol negara dan tindakannya.
sumber
Anda tidak hanya meminta status internal , Anda juga tidak bertanya apakah itu memiliki kondisi internal sama sekali .
Katakan juga , jangan tanya! tidak tidak berarti tidak mendapatkan hasil dalam bentuk nilai kembali (disediakan oleh
return
pernyataan dalam metode). Itu hanya menyiratkan saya tidak peduli bagaimana Anda melakukannya, tetapi lakukan pemrosesan itu! . Dan terkadang Anda langsung menginginkan hasil prosesnya ...sumber
next()
metode iterators seharusnya tidak hanya mengembalikan objek saat ini tetapi juga mengubah keadaan internal iterator sehingga panggilan berikutnya mengembalikan objek berikutnya ...Jika Anda dianggap
return
sebagai "berbahaya" (untuk tetap di gambar Anda), maka alih-alih membuat fungsi sepertimembangunnya dengan cara menyampaikan pesan:
Selama
f
dang
bebas efek samping, merantai mereka bersama-sama akan bebas efek samping juga. Saya pikir gaya ini mirip dengan apa yang juga disebut gaya Continuation-passing .Jika ini benar-benar mengarah ke program "lebih baik" masih bisa diperdebatkan, karena itu melanggar beberapa konvensi. Insinyur perangkat lunak Jerman Ralf Westphal membuat seluruh model pemrograman di sekitar ini, ia menyebutnya "Event Based Components" dengan teknik pemodelan yang ia sebut "Flow Design".
Untuk melihat beberapa contoh, mulai di bagian "Menerjemahkan ke Acara" pada entri blog ini . Untuk pendekatan penuh, saya merekomendasikan e-book-nya "Pesan sebagai model Pemrograman - Melakukan OOP seolah-olah Anda bersungguh-sungguh" .
sumber
If this really leads to "better" programs is debatable
Kita hanya perlu melihat kode yang ditulis dalam JavaScript selama dekade pertama abad ini. Jquery dan pluginsnya rentan terhadap paradigma callback ini ... callback di mana-mana . Pada titik tertentu, terlalu banyak panggilan balik bersarang , menjadikan debugging sebagai mimpi buruk. Kode masih harus dibaca oleh manusia terlepas dari keeksentrikan dari rekayasa perangkat lunak dan "prinsip-prinsipnya"return
data dari fungsi dan masih mematuhi Tell Don't Ask, selama Anda tidak mengambil alih status objek.Pesan yang lewat secara inheren efektif. Jika Anda memberi tahu objek untuk melakukan sesuatu, Anda berharap itu memiliki efek pada sesuatu. Jika penangan pesan murni, Anda tidak perlu mengirimnya pesan.
Dalam sistem aktor terdistribusi, hasil operasi biasanya dikirim sebagai pesan kembali ke pengirim permintaan asli. Pengirim pesan secara implisit disediakan oleh runtime aktor, atau (secara konvensional) diteruskan secara eksplisit sebagai bagian dari pesan tersebut. Dalam pengiriman pesan yang sinkron, satu respons mirip dengan
return
pernyataan. Dalam pengiriman pesan yang tidak sinkron, menggunakan pesan respons sangat berguna karena memungkinkan untuk pemrosesan bersamaan di banyak aktor sambil tetap memberikan hasil.Melewati "pengirim" yang hasilnya harus dikirim secara eksplisit pada dasarnya memodelkan gaya kelanjutan meneruskan atau parameter yang ditakuti - kecuali bahwa ia mengirimkan pesan kepada mereka alih-alih memutasikan mereka secara langsung.
sumber
Seluruh pertanyaan ini menurut saya sebagai 'pelanggaran tingkat'.
Anda memiliki (setidaknya) level berikut dalam proyek besar:
Dan seterusnya ke token individu.
Sebenarnya tidak ada kebutuhan untuk entitas pada tingkat metode / fungsi untuk tidak kembali (bahkan jika itu hanya kembali
this
). Dan tidak ada (dalam deskripsi Anda) kebutuhan untuk entitas di tingkat Aktor untuk mengembalikan apa pun (tergantung pada bahasa yang bahkan tidak mungkin). Saya pikir kebingungan dalam menyatukan kedua level tersebut, dan saya berpendapat bahwa mereka harus beralasan dengan jelas (bahkan jika objek yang diberikan benar-benar menjangkau beberapa level).sumber
Anda menyebutkan bahwa Anda ingin menyesuaikan diri dengan prinsip OOP "katakan, jangan tanya" dan prinsip fungsional dari fungsi murni, tetapi saya tidak begitu mengerti bagaimana hal itu membuat Anda menghindari pernyataan pengembalian.
Cara alternatif yang relatif umum untuk mengikuti kedua prinsip ini adalah dengan mengikuti semua pernyataan pengembalian dan menggunakan objek tidak permanen dengan pengambil saja. Maka pendekatannya adalah agar beberapa getter mengembalikan objek yang mirip dengan status baru, sebagai lawan mengubah keadaan objek asli.
Salah satu contoh dari pendekatan ini adalah dalam tipe data Python builtin
tuple
danfrozenset
. Berikut ini adalah penggunaan khas frozenset:Yang akan mencetak berikut ini, menunjukkan bahwa metode penyatuan menciptakan frozenset baru dengan statusnya sendiri tanpa mempengaruhi objek lama:
Contoh ekstensif lain dari struktur data tidak berubah yang serupa adalah perpustakaan Immutable.js Facebook . Dalam kedua kasus Anda mulai dengan blok bangunan ini dan dapat membangun objek domain tingkat tinggi yang mengikuti prinsip yang sama, mencapai pendekatan OOP fungsional, yang membantu Anda merangkum data dan alasan tentang hal itu dengan lebih mudah. Dan kekekalan juga memungkinkan Anda menuai manfaat karena dapat berbagi objek tersebut di antara utas tanpa harus khawatir tentang kunci.
sumber
Saya sudah mencoba yang terbaik untuk mendamaikan beberapa manfaat, lebih khusus, pemrograman imperatif dan fungsional (secara alami tidak mendapatkan semua manfaat apa pun, tetapi mencoba untuk mendapatkan bagian terbesar dari keduanya), meskipun
return
sebenarnya penting untuk melakukan itu di mode sederhana bagi saya dalam banyak kasus.Sehubungan dengan mencoba menghindari
return
pernyataan langsung, saya mencoba untuk merenungkan hal ini selama satu jam terakhir dan pada dasarnya menumpuk meluap otak saya beberapa kali. Saya dapat melihat daya tariknya dalam hal menegakkan tingkat enkapsulasi dan informasi terkuat yang mendukung objek-objek yang sangat otonom yang hanya diberi tahu apa yang harus dilakukan, dan saya suka menjelajahi ekstremitas gagasan jika hanya mencoba untuk menjadi lebih baik memahami bagaimana mereka bekerja.Jika kita menggunakan contoh lampu lalu lintas, maka segera upaya naif akan ingin memberikan pengetahuan lampu lalu lintas seperti itu dari seluruh dunia yang mengelilinginya, dan itu tentu saja tidak diinginkan dari perspektif penggandengan. Jadi, jika saya mengerti dengan benar, Anda abstrak itu dan berpisah demi generalisasi konsep port I / O yang selanjutnya menyebarkan pesan dan permintaan, bukan data, melalui pipa, dan pada dasarnya menyuntikkan objek-objek ini dengan interaksi / permintaan yang diinginkan antara satu sama lain sementara tidak menyadari satu sama lain.
Pipa Nodal
Dan diagram itu adalah tentang sejauh saya mencoba untuk membuat sketsa ini (dan walaupun sederhana, saya harus terus mengubahnya dan memikirkannya kembali). Segera saya cenderung berpikir desain dengan tingkat decoupling dan abstraksi ini akan menemukan jalannya menjadi sangat sulit untuk dipertimbangkan dalam bentuk kode, karena orkestra yang menghubungkan semua hal ini ke dunia yang kompleks mungkin merasa sangat sulit untuk melacak semua interaksi dan permintaan ini untuk membuat saluran pipa yang diinginkan. Namun, dalam bentuk visual, mungkin cukup mudah untuk hanya menggambar hal-hal ini sebagai grafik dan menghubungkan semuanya dan melihat hal-hal terjadi secara interaktif.
Dalam hal efek samping, saya bisa melihat ini bebas dari "efek samping" dalam arti bahwa permintaan ini dapat, pada tumpukan panggilan, mengarah ke rantai perintah untuk setiap utas untuk melakukan, misalnya (saya tidak menghitung ini sebagai "efek samping" dalam arti pragmatis karena tidak mengubah keadaan yang relevan dengan dunia luar sampai perintah tersebut benar-benar dieksekusi - tujuan praktis bagi saya dalam sebagian besar perangkat lunak bukan untuk menghilangkan efek samping tetapi menunda dan memusatkannya) . Dan lebih jauh lagi, eksekusi perintah dapat menghasilkan dunia baru yang bertentangan dengan mutasi yang sudah ada. Otak saya benar-benar dikenakan pajak hanya mencoba memahami semua ini namun, tidak ada upaya prototipe ide-ide ini. Saya juga tidak
Bagaimana itu bekerja
Jadi untuk memperjelas saya membayangkan bagaimana Anda sebenarnya memprogram ini. Cara saya melihatnya bekerja sebenarnya adalah diagram di atas yang menangkap alur kerja user-end (programmer). Anda dapat menyeret lampu lalu lintas ke dunia, menyeret pengatur waktu, memberinya periode yang telah berlalu (saat "membuat" itu). Timer memiliki
On Interval
acara (port keluaran), Anda dapat menghubungkannya ke lampu lalu lintas sehingga pada acara semacam itu, ia memberi tahu lampu untuk menelusuri warna-warnanya.Lalu lintas lampu mungkin, pada beralih ke warna tertentu, memancarkan output (peristiwa) seperti,,
On Red
pada titik mana kita mungkin menyeret pejalan kaki ke dunia kita dan membuat acara itu memberitahu pejalan kaki untuk mulai berjalan ... atau kita mungkin menyeret burung ke pemandangan kami dan membuatnya jadi ketika cahaya berubah menjadi merah, kami memberitahu burung untuk mulai terbang dan mengepakkan sayap mereka ... atau mungkin ketika cahaya berubah menjadi merah, kami memberi tahu sebuah bom untuk meledak - apa pun yang kita inginkan, dan dengan benda-benda menjadi sepenuhnya tidak menyadari satu sama lain, dan tidak melakukan apa pun kecuali secara tidak langsung saling memberi tahu apa yang harus dilakukan melalui konsep input / output abstrak ini.Dan mereka sepenuhnya merangkum keadaan mereka dan tidak mengungkapkan apa-apa tentang hal itu (kecuali jika "peristiwa" ini dianggap sebagai TMI, pada titik mana saya harus memikirkan kembali banyak hal), mereka mengatakan satu sama lain untuk melakukan secara tidak langsung, mereka tidak bertanya. Dan mereka terpisah. Tidak ada yang tahu tentang apa pun kecuali abstraksi port input / output umum ini.
Kasus Penggunaan Praktis?
Saya bisa melihat jenis hal ini berguna sebagai bahasa tertanam tingkat-domain-spesifik tingkat tinggi dalam domain tertentu untuk mengatur semua objek otonom ini yang tidak tahu apa-apa tentang dunia sekitarnya, tidak mengekspos apa pun dari konstruksi pos negara internal mereka, dan pada dasarnya hanya menyebarkan permintaan antara satu sama lain yang bisa kita ubah dan sesuaikan dengan isi hati kita. Saat ini saya merasa ini sangat spesifik untuk domain, atau mungkin saya belum cukup memikirkannya, karena sangat sulit bagi saya untuk membungkus otak saya dengan hal-hal yang saya kembangkan secara teratur (saya sering bekerja dengan kode tingkat menengah ke bawah) jika saya menafsirkan Tell, Don't Ask untuk ekstremitas seperti itu dan menginginkan tingkat enkapsulasi terkuat yang bisa dibayangkan. Tetapi jika kita bekerja dengan abstraksi tingkat tinggi dalam domain tertentu,
Sinyal dan Slot
Desain ini tampak aneh bagi saya sampai saya menyadari itu pada dasarnya sinyal dan slot jika kita tidak mengambil banyak nuansa bagaimana itu diterapkan ke dalam akun. Pertanyaan utama bagi saya adalah seberapa efektif kita dapat memprogram masing-masing node (objek) ini dalam grafik yang secara ketat mengikuti Tell, Don't Ask, sampai pada tingkat menghindari
return
pernyataan, dan apakah kita dapat mengevaluasi grafik tersebut tanpa mutasi (dalam paralel, misalnya, tidak ada penguncian). Di situlah manfaat magisnya bukanlah dalam cara kita menghubungkan hal-hal ini bersama-sama secara potensial, tetapi bagaimana mereka dapat diimplementasikan ke tingkat enkapsulasi ini tanpa adanya mutasi. Kedua hal ini tampaknya layak untuk saya, tetapi saya tidak yakin seberapa luas itu akan berlaku, dan di situlah saya agak bingung mencoba untuk bekerja melalui kasus penggunaan potensial.sumber
Saya jelas melihat kebocoran kepastian di sini. Tampaknya "efek samping" adalah istilah yang dikenal dan dipahami secara umum, tetapi kenyataannya tidak. Bergantung pada definisi Anda (yang sebenarnya hilang dalam OP), efek samping mungkin benar-benar diperlukan (seperti yang dijelaskan oleh @JacquesB), atau tanpa ampun diterima. Atau, membuat satu langkah menuju klarifikasi, ada kebutuhan untuk membedakan antara efek samping yang diinginkan yang tidak ingin disembunyikan (pada titik ini IO Haskell yang terkenal muncul: itu hanyalah cara untuk menjadi eksplisit) dan efek samping yang tidak diinginkan sebagai hasil dari bug kode dan hal-hal semacam itu . Itu adalah masalah yang sangat berbeda dan karenanya memerlukan alasan yang berbeda.
Jadi, saya sarankan untuk memulai dari pengubahan ulang kata-kata Anda sendiri: "Bagaimana kita mendefinisikan efek samping dan apa yang diberikan definisi tentang keterkaitannya dengan pernyataan" kembali "?".
sumber