Bagaimana para pendukung Pemrograman Fungsional menjawab pernyataan ini dalam Kode Lengkap?

30

Pada halaman 839 edisi kedua, Steve McConnell sedang mendiskusikan semua cara yang bisa dilakukan "programmer menaklukkan kompleksitas" dalam program besar. Kiatnya berujung pada pernyataan ini:

"Pemrograman berorientasi objek menyediakan tingkat abstraksi yang berlaku untuk algoritma dan data pada saat yang sama , semacam abstraksi yang tidak disediakan oleh dekomposisi fungsional."

Ditambah dengan kesimpulannya bahwa "mengurangi kompleksitas bisa dibilang kunci terpenting untuk menjadi programmer yang efektif" (halaman yang sama), ini tampaknya cukup banyak tantangan untuk pemrograman fungsional.

Perdebatan antara FP dan OO sering dibingkai oleh para pendukung FP tentang masalah kompleksitas yang berasal secara khusus dari tantangan konkurensi atau paralelisasi. Tetapi concurrency tentu saja bukan satu-satunya jenis kerumitan yang perlu ditaklukkan oleh pemrogram perangkat lunak. Mungkin berfokus pada pengurangan satu jenis kompleksitas akan sangat meningkatkannya di dimensi lain, sehingga bagi banyak kasus, keuntungannya tidak sebanding dengan biayanya.

Jika kita menggeser ketentuan perbandingan antara FP dan OO dari isu-isu tertentu seperti konkurensi atau penggunaan kembali ke pengelolaan kompleksitas global, bagaimana perdebatan itu terlihat?

EDIT

Kontras yang ingin saya soroti adalah bahwa OO tampaknya merangkum dan abstrak jauh dari kompleksitas data dan algoritma, sedangkan pemrograman fungsional tampaknya mendorong meninggalkan detail implementasi struktur data yang lebih "terbuka" di seluruh program.

Lihat, misalnya, Stuart Halloway (pendukung Clojure FP) di sini mengatakan bahwa "spesifikasi berlebih dari tipe data" adalah "konsekuensi negatif dari gaya OO idiomatik" dan lebih memilih untuk mengonseptualisasikan Buku Alamat sebagai vektor atau peta sederhana alih-alih objek OO yang lebih kaya. dengan properti dan metode tambahan (non-vektor & non-maplike). (Juga, para pendukung OO dan Desain Berbasis Domain dapat mengatakan bahwa mengekspos Buku Alamat sebagai vektor atau peta mengekspos data enkapsulasi dengan metode yang tidak relevan atau bahkan berbahaya dari sudut pandang domain).

dan
sumber
3
+1 meskipun pertanyaannya telah dibingkai agak antagonis, itu pertanyaan yang bagus.
mattnz
16
Seperti yang telah dinyatakan dalam jawaban, Dekomposisi Fungsional dan Pemrograman fungsional adalah dua binatang yang berbeda. Jadi kesimpulan bahwa "ini tampaknya merupakan tantangan bagi pemrograman fungsional" jelas salah, itu tidak ada hubungannya dengan itu.
Fabio Fracassi
5
Jelas pengetahuan McConnel dalam sistem tipe data fungsional modern dan modul kelas pertama agak tambal sulam. Pernyataannya benar-benar omong kosong, karena kita punya modul kelas pertama dan functors (lihat SML), ketik kelas (lihat Haskell). Ini hanyalah contoh lain tentang bagaimana cara berpikir OO lebih merupakan agama daripada metodologi desain yang terhormat. Dan, omong-omong, dari mana Anda mendapatkan hal ini tentang konkurensi? Sebagian besar programmer fungsional tidak peduli sama sekali tentang paralelisme.
SK-logic
6
@ SK-logika Semua yang dikatakan McConnell adalah "dekomposisi fungsional saja" tidak menyediakan sarana abstraksi yang sama seperti OOP, yang tampaknya merupakan pernyataan yang cukup aman bagi saya. Dia mengatakan bahwa bahasa FP tidak memiliki sarana abstraksi sekuat OOP. Bahkan dia tidak menyebutkan bahasa FP sama sekali. Itu hanya interpretasi OP.
sepp2k
2
@ sepp2k, ok, saya mengerti. Namun demikian, sistem struktur data dan pemrosesan abstraksi yang sangat kompleks dan tersusun dengan baik dapat dibangun di atas apa pun kecuali dekomposisi fungsional untuk kalkulus lambda yang hampir murni - melalui simulasi perilaku modul. Tidak perlu untuk abstraksi OO sama sekali.
SK-logic

Jawaban:

13

Ingatlah bahwa buku itu ditulis lebih dari 20 tahun. Bagi programmer profesional saat itu, FP tidak ada - itu sepenuhnya berada di ranah akademisi & peneliti.

Kita perlu membingkai "dekomposisi fungsional" dalam konteks pekerjaan yang tepat. Penulis tidak mengacu pada pemrograman fungsional. Kita perlu mengikat ini kembali ke "pemrograman terstruktur" dan GOTOkekacauan penuh yang datang sebelumnya. Jika titik referensi Anda adalah FORTRAN / COBOL / BASIC lama yang tidak memiliki fungsi (mungkin, jika Anda beruntung Anda akan mendapatkan satu tingkat GOSUB) dan semua variabel Anda bersifat global, dapat memecah program Anda ke dalam lapisan fungsi adalah anugerah utama.

OOP adalah penyempurnaan lebih lanjut pada 'dekomposisi fungsional' semacam ini. Anda tidak hanya dapat menggabungkan instruksi bersama dalam fungsi-fungsi tetapi Anda juga dapat mengelompokkan fungsi - fungsi terkait dengan data yang sedang mereka kerjakan. Hasilnya adalah potongan kode yang jelas yang dapat Anda lihat dan pahami (idealnya) tanpa harus mengejar seluruh basis kode Anda untuk menemukan apa lagi yang mungkin beroperasi pada data Anda.

Sean McSomething
sumber
27

Saya membayangkan pendukung pemrograman fungsional akan berpendapat bahwa sebagian besar bahasa FP menyediakan lebih banyak sarana abstraksi daripada "dekomposisi fungsional saja" dan memang memungkinkan sarana abstraksi yang sebanding dalam kekuasaan dengan yang dari Bahasa Berorientasi Objek. Sebagai contoh, seseorang dapat mengutip kelas tipe Haskell atau modul tingkat tinggi ML sebagai sarana abstraksi. Jadi pernyataan (yang saya cukup yakin adalah tentang orientasi objek vs pemrograman prosedural, bukan pemrograman fungsional) tidak berlaku untuk mereka.

Juga harus ditunjukkan bahwa FP dan OOP adalah konsep ortogonal dan tidak saling eksklusif. Jadi tidak masuk akal untuk membandingkan mereka satu sama lain. Anda bisa membandingkan "imperative OOP" (mis. Java) vs. "fungsional OOP" (mis. Scala), tetapi pernyataan yang Anda kutip tidak berlaku untuk perbandingan itu.

sepp2k
sumber
10
+1 "Dekomposisi fungsional"! = "Pemrograman fungsional". Yang pertama bergantung pada pengkodean sekuensial klasik menggunakan struktur data vanilla tanpa (atau hanya digulung dengan tangan) pewarisan, enkapsulasi & polimorfisme. Yang kedua mengungkapkan solusi menggunakan kalkulus lambda. Dua hal yang sangat berbeda.
Binary Worrier
4
Permintaan maaf, tetapi frasa "pemrograman prosedural" dengan keras kepala menolak untuk datang ke pikiran sebelumnya. "Dekomposisi fungsional" bagi saya jauh lebih menunjukkan pemrograman prosedural daripada pemrograman fungsional.
Binary Worrier
Ya kau benar. Saya memang berasumsi bahwa Pemrograman Fungsional mendukung fungsi yang dapat digunakan kembali yang beroperasi pada struktur data sederhana yang sama (daftar, pohon, peta) berulang-ulang & sebenarnya mengklaim bahwa ini adalah nilai jual OO. Lihat Stuart Halloway (pendukung Clojure FP) di sini mengatakan bahwa "spesifikasi data yang berlebih" adalah "konsekuensi negatif dari gaya OO idiomatik" dan lebih memilih untuk mengonseptualisasikan Buku Alamat sebagai vektor atau peta alih-alih objek OO yang lebih kaya dengan yang lain (bukan -vectorish & non-maplike) properti dan metode.
dan
Tautan untuk kutipan Stuart Halloway: thinkrelevance.com/blog/2009/08/12/…
dan
2
@dan Itu mungkin cara itu dilakukan dalam bahasa yang diketik secara dinamis Clojure (saya tidak tahu, saya tidak menggunakan Clojure), tapi saya pikir berbahaya untuk menyimpulkan dari itu, bahwa itulah yang dilakukan di FP pada umumnya. Orang-orang Haskell, misalnya, tampaknya sangat besar pada tipe abstrak dan menyembunyikan informasi (mungkin tidak sebanyak orang Jawa sekalipun).
sepp2k
7

Saya menemukan pemrograman fungsional sangat membantu dalam mengelola kompleksitas. Anda cenderung berpikir tentang kompleksitas dengan cara yang berbeda, mendefinisikannya sebagai fungsi yang bekerja pada data yang tidak dapat diubah pada tingkat yang berbeda daripada enkapsulasi dalam arti OOP.

Sebagai contoh, saya baru-baru ini menulis sebuah game di Clojure, dan seluruh kondisi permainan didefinisikan dalam struktur data tunggal yang tidak dapat diubah:

(def starting-game-state {:map ....
                          :player ....
                          :weather ....
                          :other-stuff ....}

Dan loop game utama dapat didefinisikan sebagai menerapkan beberapa fungsi murni ke status game dalam satu loop:

 (loop [initial-state starting-game-state]
   (let [user-input (get-user-input)
         game-state (update-game initial-state user-input)]
     (draw-screen game-state)
     (if-not (game-ended? game-state) (recur game-state))))

Fungsi utama yang dipanggil adalah update-game, yang menjalankan langkah simulasi mengingat kondisi permainan sebelumnya dan beberapa input pengguna, dan mengembalikan status permainan baru.

Jadi di mana kompleksitasnya? Dalam pandangan saya itu telah dikelola dengan cukup baik:

  • Tentu saja fungsi pembaruan-permainan melakukan banyak pekerjaan, tetapi itu sendiri dibangun dengan menyusun fungsi-fungsi lain sehingga sebenarnya itu sendiri cukup sederhana. Setelah Anda turun beberapa level, fungsinya masih cukup sederhana, melakukan sesuatu seperti "menambahkan objek ke ubin peta".
  • Tentu saja status permainan adalah struktur data besar. Tetapi sekali lagi, itu hanya dibangun dengan menyusun struktur data tingkat yang lebih rendah. Ini juga "data murni" daripada memiliki metode yang disematkan atau dan definisi kelas diperlukan (Anda dapat menganggapnya sebagai objek JSON abadi yang sangat efisien jika Anda mau) sehingga hanya ada sedikit boilerplate.

OOP juga dapat mengelola kompleksitas melalui enkapsulasi, tetapi jika Anda membandingkannya dengan OOP, fungsional memiliki beberapa keuntungan yang sangat besar:

  • Struktur data status permainan tidak dapat diubah, sehingga banyak pemrosesan dapat dengan mudah dilakukan secara paralel. Sebagai contoh, sangat aman untuk memiliki rendering panggilan menggambar di thread yang berbeda dari logika game - mereka tidak dapat saling mempengaruhi atau melihat kondisi yang tidak konsisten. Ini sangat sulit dengan grafik objek yang bisa berubah besar ......
  • Anda dapat mengambil snapshot dari kondisi permainan kapan saja. Replay itu sepele (terima kasih kepada struktur data persisten Clojure, salinannya hampir tidak memakan memori apa pun karena sebagian besar data dibagikan). Anda juga dapat menjalankan pembaruan-game untuk "memprediksi masa depan" untuk membantu AI mengevaluasi berbagai gerakan, misalnya.
  • Tidak ada tempat di mana saya harus melakukan trade-off yang sulit agar sesuai dengan paradigma OOP, seperti mendefinisikan heirarki kelas yang kaku. Dalam hal ini struktur data fungsional berperilaku lebih seperti sistem berbasis prototipe yang fleksibel.

Akhirnya, bagi orang-orang yang tertarik pada wawasan lebih lanjut tentang cara mengelola kompleksitas dalam bahasa fungsional vs. bahasa OOP, saya sangat merekomendasikan video pidato utama Rich Hickey, Simple Made Easy (difilmkan pada konferensi teknologi Strange Loop )

mikera
sumber
2
Saya akan berpikir permainan adalah salah satu contoh terburuk untuk menunjukkan "manfaat" dari ketidakberdayaan yang dipaksakan. Hal-hal terus bergerak dalam permainan, yang berarti Anda harus membangun kembali kondisi permainan Anda setiap saat. Dan jika semuanya tidak berubah, itu berarti Anda tidak hanya harus membangun kembali kondisi permainan, tetapi semua yang ada di grafik yang menyimpan referensi untuk itu, atau yang memegang referensi untuk itu, dan seterusnya secara rekursif hingga Anda mendaur ulang keseluruhan program di 30+ FPS, dengan banyak churn GC untuk di-boot! Tidak ada cara Anda mendapatkan kinerja yang baik dari itu ...
Mason Wheeler
7
Tentu saja permainan sulit dengan kekekalan - itu sebabnya saya memilihnya untuk menunjukkan bahwa itu masih bisa bekerja! Namun Anda akan terkejut dengan apa yang dapat dilakukan oleh struktur data yang persisten - sebagian besar kondisi permainan tidak perlu dibangun kembali, hanya hal-hal yang berubah. Dan tentu saja ada beberapa overhead, tetapi itu hanya faktor konstan kecil. Beri saya core yang cukup dan saya akan mengalahkan mesin game C ++ single-threaded Anda .....
mikera
3
@Mason Wheeler: Sebenarnya, adalah mungkin untuk mendapatkan hampir sama (sebagus dengan mutasi) kinerja dengan objek abadi, tanpa banyak GC sama sekali. Trik dalam Clojure adalah menggunakan struktur data yang persisten : mereka tidak dapat diubah-ke-programmer, tetapi sebenarnya bisa berubah di bawah tenda. Terbaik dari kedua dunia.
Joonas Pulakka
4
@quant_dev Lebih banyak core lebih murah daripada core yang lebih baik ... escapistmagazine.com/news/view/…
deworde
6
@quant_dev - ini bukan alasan, ini adalah fakta matematika dan arsitektur yang lebih baik Anda mengeluarkan overhead konstan jika Anda dapat menebusnya dengan meningkatkan kinerja Anda dekat-linear dengan jumlah core. Alasan bahasa fungsional pada akhirnya akan menawarkan kinerja yang unggul adalah bahwa kita telah sampai pada akhir garis untuk kinerja inti tunggal, dan itu semua akan tentang konkurensi dan paralelisme di masa depan. pendekatan fungsional (dan kekekalan khususnya) penting dalam membuat pekerjaan ini.
mikera
3

"Pemrograman berorientasi objek menyediakan tingkat abstraksi yang berlaku untuk algoritma dan data pada saat yang sama, semacam abstraksi yang tidak disediakan oleh dekomposisi fungsional."

Dekomposisi fungsional saja tidak cukup untuk membuat algoritma atau program apa pun: Anda perlu merepresentasikan data juga. Saya pikir pernyataan di atas secara implisit mengasumsikan (atau setidaknya dapat dipahami seperti) bahwa "data" dalam kasus fungsional adalah jenis yang paling dasar: hanya daftar simbol dan tidak ada yang lain. Memprogram dalam bahasa seperti itu jelas sangat tidak nyaman. Namun, banyak, terutama bahasa baru dan modern, fungsional (atau multi-paradigma), seperti Clojure, menawarkan struktur data yang kaya: tidak hanya daftar, tetapi juga string, vektor, peta dan set, catatan, struct - dan objek! - dengan metadata dan polimorfisme.

Keberhasilan besar praktis dari abstraksi OO hampir tidak dapat diperdebatkan. Tapi apakah itu kata terakhir? Seperti yang Anda tulis, masalah konkurensi sudah menjadi masalah besar, dan OO klasik tidak mengandung gagasan konkurensi sama sekali. Akibatnya, solusi OO de facto untuk menangani konkurensi hanyalah lakban yang dilapiskan: berfungsi, tetapi mudah untuk dikacaukan, mengambil banyak sumber daya otak dari tugas penting yang ada, dan tidak dapat diukur dengan baik. Mungkin mungkin untuk mengambil yang terbaik dari banyak dunia. Itulah yang dikejar oleh bahasa multi-paradigma modern.

Joonas Pulakka
sumber
1
Saya pernah mendengar ungkapan "OO di besar, FP di kecil" di suatu tempat - saya pikir Michael Feathers mengutipnya. Artinya, FP itu mungkin baik untuk bagian-bagian tertentu dari program besar, tetapi secara umum, itu harus OO.
dan
Juga, alih-alih menggunakan Clojure untuk semuanya, bahkan hal-hal yang diekspresikan lebih bersih dalam sintaksis OO yang lebih tradisional, bagaimana dengan menggunakan Clojure untuk bit pemrosesan data yang lebih bersih, dan menggunakan Java atau bahasa OO lainnya untuk bit lainnya. Pemrograman polyglot bukan pemrograman multiparadigma dengan bahasa yang sama untuk semua bagian program. (Semacam seperti bagaimana sebagian besar aplikasi web menggunakan SQL dan OO untuk berbagai lapisan.)?
dan
@dan: Gunakan alat apa pun yang paling cocok dengan pekerjaan itu. Dalam pemrograman polyglot, faktor penting adalah komunikasi yang nyaman antara bahasa, dan Clojure dan Java hampir tidak bisa bermain lebih baik bersama . Saya percaya sebagian besar program Clojure menggunakan setidaknya beberapa bit standar library JDK di sana-sini.
Joonas Pulakka
2

Keadaan berubah-ubah adalah akar dari sebagian besar kompleksitas dan masalah yang terkait dengan pemrograman dan perancangan perangkat lunak / sistem.

OO mencakup keadaan yang bisa berubah. FP membenci keadaan bisa berubah.

Baik OO dan FP memiliki kegunaan & sweet spot mereka. Pilihlah dengan bijak. Dan ingat pepatah: "Penutupan adalah benda orang miskin. Objek adalah penutupan orang miskin."

Maglob
sumber
3
Saya tidak yakin pernyataan pembuka Anda benar. Akar "sebagian besar" dari kompleksitas? Dalam pemrograman yang saya lakukan atau lihat, masalahnya bukan keadaan yang bisa berubah-ubah karena kurangnya abstraksi dan meluap-luapnya detail melalui kode.
dan
1
@Dan: Menarik. Saya telah melihat banyak hal yang berlawanan, sebenarnya: Masalah berasal dari penggunaan yang berlebihan dari abstraksi, membuatnya sulit untuk dipahami dan, jika perlu, untuk memperbaiki detail dari apa yang sebenarnya terjadi.
Mason Wheeler
1

Pemrograman Fungsional dapat memiliki objek, tetapi objek tersebut cenderung tidak berubah. Fungsi murni (fungsi tanpa efek samping) kemudian beroperasi pada struktur data tersebut. Dimungkinkan untuk membuat objek yang tidak berubah dalam bahasa pemrograman berorientasi objek, tetapi mereka tidak dirancang untuk melakukannya dan itu bukan cara mereka cenderung digunakan. Ini membuatnya sulit untuk beralasan tentang program berorientasi objek.

Mari kita ambil contoh yang sangat sederhana. Katakanlah Oracle memutuskan bahwa Java Strings harus memiliki metode terbalik dan Anda menulis kode berikut.

String x = "abc";
StringBuffer y = new StringBuffer(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString());

apa yang baris terakhir evaluasi? Anda memerlukan pengetahuan khusus tentang kelas String untuk mengetahui bahwa ini akan bernilai false.

Bagaimana jika saya membuat WuHoString kelas saya sendiri

String x = "abc";
WuHoString y = new WuHoString(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString())

Tidak mungkin untuk mengetahui apa yang dinilai oleh baris terakhir.

Dalam gaya Pemrograman Fungsional akan ditulis lebih sebagai berikut:

String x;
equals(toString(reverse(x)), toString(reverse(WuHoString(x))))

dan itu harus benar.

Jika 1 fungsi di salah satu kelas paling dasar sangat sulit untuk dipikirkan, maka orang bertanya-tanya apakah memperkenalkan ide objek yang bisa berubah ini telah menambah atau mengurangi kompleksitas.

Jelas ada segala macam definisi tentang apa yang merupakan berorientasi objek dan apa artinya menjadi fungsional dan apa artinya memiliki keduanya. Bagi saya Anda dapat memiliki "gaya pemrograman fungsional" dalam bahasa yang tidak memiliki hal-hal seperti fungsi kelas satu tetapi bahasa lain dibuat untuk itu.

WuHoUnited
sumber
3
Agak lucu bahwa Anda mengatakan bahwa bahasa OO tidak dibangun untuk objek yang tidak dapat diubah dan kemudian menggunakan contoh dengan string (yang tidak dapat diubah di sebagian besar OO-bahasa termasuk Jawa). Saya juga harus menunjukkan bahwa ada bahasa OO (atau lebih tepatnya multi-paradigma) yang dirancang dengan penekanan pada objek yang tidak dapat diubah (Scala misalnya).
sepp2k
@ sepp2k: Biasakan. Advokat FP selalu melemparkan contoh-contoh aneh yang dibuat-buat yang tidak ada hubungannya dengan pengkodean dunia nyata. Ini satu-satunya cara untuk membuat konsep inti FP seperti imutabilitas yang dipaksakan terlihat bagus.
Mason Wheeler
1
@ Alasan: Hah? Tidakkah cara terbaik untuk membuat kekekalan terlihat bagus adalah dengan mengatakan "Java (dan C #, python, dll) menggunakan string yang tidak dapat diubah dan itu bekerja dengan baik"?
sepp2k
1
@ sepp2k: Jika string yang tidak dapat diubah berfungsi dengan sangat baik, mengapa kelas gaya StringBuilder / StringBuffer terus muncul di seluruh? Ini hanyalah contoh lain dari inversi abstraksi yang menghalangi Anda.
Mason Wheeler
2
Banyak bahasa berorientasi objek memungkinkan Anda membuat objek yang tidak dapat diubah. Tetapi konsep mengikat metode ke kelas benar-benar mengecilkannya dari sudut pandang saya. Contoh String sebenarnya bukan contoh yang dibuat-buat. Setiap kali saya memanggil mehtod di java, saya mengambil kesempatan apakah parameter saya akan dimutasi dalam fungsi itu.
WuHoUnited
0

Saya pikir dalam banyak kasus abstraksi OOP klasik tidak mencakup kompleksitas konkurensi. Oleh karena itu OOP (dengan makna aslinya) tidak mengecualikan FP, dan itulah mengapa kita melihat hal-hal seperti scala.

duyt
sumber
0

Jawabannya tergantung pada bahasanya. Lisps, misalnya, memiliki benar-benar rapi benar bahwa kode adalah data - algoritma Anda menulis sebenarnya hanya Lisp daftar! Anda menyimpan data dengan cara yang sama seperti Anda menulis program. Abstraksi ini secara simultan lebih sederhana dan lebih teliti daripada OOP dan memungkinkan Anda melakukan beberapa hal yang sangat rapi (lihat makro).

Haskell (dan bahasa yang serupa, saya bayangkan) memiliki jawaban yang sama sekali berbeda: tipe data aljabar. Tipe data aljabar seperti Cstruct, tetapi dengan lebih banyak opsi. Tipe data ini menyediakan abstraksi yang dibutuhkan untuk memodelkan data; fungsi menyediakan abstraksi yang diperlukan untuk memodelkan algoritma. Jenis kelas dan fitur canggih lainnya memberikan bahkan lebih tinggi tingkat abstraksi lebih baik.

Sebagai contoh, saya sedang mengerjakan bahasa pemrograman yang disebut TPL untuk bersenang-senang. Tipe data aljabar membuatnya sangat mudah untuk mewakili nilai:

data TPLValue = Null
              | Number Integer
              | String String
              | List [TPLValue]
              | Function [TPLValue] TPLValue
              -- There's more in the real code...

Apa yang dikatakan ini - dengan cara yang sangat visual - adalah bahwa TPLValue (nilai apa pun dalam bahasa saya) dapat berupa a Nullatau a Numberdengan Integernilai atau bahkan Functiondengan daftar nilai (parameter) dan nilai akhir (badan ).

Selanjutnya saya bisa menggunakan kelas tipe untuk menyandikan beberapa perilaku umum. Sebagai contoh, saya bisa membuat TPLValuedan contoh Showyang artinya dapat dikonversi ke string.

Selain itu, saya bisa menggunakan kelas tipe saya sendiri ketika saya perlu menentukan perilaku tipe tertentu (termasuk yang saya tidak menerapkan sendiri). Sebagai contoh, saya memiliki Extractablekelas tipe yang memungkinkan saya menulis fungsi yang mengambil TPLValuedan mengembalikan nilai normal yang sesuai. Dengan demikian extractdapat mengkonversi a Numberke Integeratau Stringke Stringselama Integerdan Stringmerupakan contoh Extractable.

Akhirnya, logika utama program saya adalah dalam beberapa fungsi seperti evaldan apply. Ini benar-benar inti - mereka mengambil TPLValuedan mengubahnya menjadi lebih banyak TPLValue, serta menangani keadaan dan kesalahan.

Secara keseluruhan, abstraksi yang saya gunakan dalam kode Haskell saya sebenarnya lebih kuat daripada apa yang akan saya gunakan dalam bahasa OOP.

Tikhon Jelvis
sumber
Ya, pasti cinta eval. "Hei, lihat aku! Aku tidak perlu menulis lubang keamananku sendiri; Aku punya kerentanan eksekusi kode arbitrer yang dibangun langsung ke dalam bahasa pemrograman!" Penggabungan data dengan kode adalah akar penyebab salah satu dari dua kelas kerentanan keamanan paling populer sepanjang masa. Setiap kali Anda melihat seseorang diretas karena serangan injeksi SQL (di antara banyak hal lain) itu karena beberapa programmer di luar sana tidak tahu cara memisahkan data dari kode dengan benar.
Mason Wheeler
evaltidak terlalu bergantung pada struktur Lisp - Anda dapat memiliki evaldalam bahasa seperti JavaScript dan Python. Kekuatan sebenarnya datang dalam penulisan makro, yang pada dasarnya adalah program yang bekerja pada program seperti data dan output program lain. Ini membuat bahasa ini sangat fleksibel dan membuat abstraksi yang kuat menjadi mudah.
Tikhon Jelvis
3
Ya, saya pernah mendengar pembicaraan "macro is awesome" berkali-kali sebelumnya. Tetapi saya belum pernah melihat contoh makro Lisp yang sebenarnya melakukan sesuatu yang 1) praktis dan sesuatu yang benar-benar ingin Anda lakukan dalam kode dunia nyata dan 2) tidak dapat dicapai dengan mudah dalam bahasa apa pun yang mendukung fungsi.
Mason Wheeler
1
@MasonWheeler korsleting and. hubungan arus pendek or. let. let-rec. cond. defn. Tak satu pun dari ini dapat diimplementasikan dengan fungsi dalam bahasa urutan aplikatif. for(daftar pemahaman). dotimes. doto.
1
@ MattFenwick: OK, saya benar-benar harus menambahkan poin ketiga ke dua saya di atas: 3) belum built-in ke bahasa pemrograman yang waras. Karena itulah satu-satunya contoh makro yang benar-benar berguna yang pernah saya lihat, dan ketika Anda mengatakan "Hei, lihat aku, bahasa saya sangat fleksibel sehingga saya bisa menerapkan korsleting sendiri and!" Saya mendengar, "hei lihat saya, bahasa saya sangat lumpuh sehingga tidak datang dengan korsleting anddan saya harus menemukan kembali roda untuk semuanya !"
Mason Wheeler
0

Kalimat yang dikutip tidak memiliki validitas lagi, sejauh yang saya bisa lihat.

Bahasa OO kontemporer tidak dapat abstrak di atas jenis yang jenisnya tidak *, yaitu jenis dengan jenis yang lebih tinggi tidak diketahui. Sistem tipe mereka tidak memungkinkan untuk mengekspresikan ide "beberapa wadah dengan elemen Int, yang memungkinkan untuk memetakan fungsi atas elemen".

Karenanya, fungsi dasar ini seperti Haskells

fmap :: Functor f => (a -> b) -> f a -> f b 

tidak dapat ditulis dengan mudah di Java *), misalnya, setidaknya tidak dengan cara yang aman. Oleh karena itu, untuk mendapatkan fungsionalitas dasar, Anda harus menulis banyak boilerplate, karena Anda memerlukannya

  1. metode untuk menerapkan fungsi sederhana ke elemen daftar
  2. metode untuk menerapkan fungsi sederhana yang sama ke elemen array
  3. metode untuk menerapkan fungsi sederhana yang sama untuk nilai hash,
  4. .... diatur
  5. .... pohon
  6. ... 10. tes unit untuk hal yang sama

Namun, kelima metode tersebut pada dasarnya adalah kode yang sama, memberi atau mengambil beberapa. Sebaliknya, di Haskell, saya perlu:

  1. Sebuah instance Functor untuk daftar, array, peta, set, dan tree (sebagian besar sudah ditentukan sebelumnya, atau dapat diturunkan secara otomatis oleh kompiler)
  2. fungsi sederhana

Perhatikan bahwa ini tidak akan berubah dengan Java 8 (hanya saja orang dapat menerapkan fungsi lebih mudah, tetapi kemudian, justru, masalah di atas akan terwujud. Selama Anda bahkan tidak memiliki fungsi urutan yang lebih tinggi, kemungkinan besar Anda bahkan tidak dapat memahami jenis jenis apa yang lebih baik.)

Bahkan bahasa OO baru seperti Ceylon tidak memiliki jenis yang lebih tinggi. (Saya telah bertanya kepada Gavin King belakangan ini, dan dia mengatakan kepada saya, itu tidak penting saat ini.) Namun, tidak tahu tentang Kotlin.

*) Agar adil, Anda dapat memiliki Functor antarmuka yang memiliki metode fmap. Yang buruk adalah, Anda tidak bisa mengatakan: Hei, saya tahu bagaimana menerapkan fmap untuk kelas perpustakaan SuperConcurrentBlockedDoublyLinkedDequeHasMap, kompiler yang terhormat, terima bahwa mulai sekarang, semua SuperConcurrentBlockedDoublyLinkedDequeHasMaps adalah Functors.

Ingo
sumber
FTR: yang typechecker Ceylon dan JavaScript backend sekarang melakukan dukungan yang lebih tinggi tangan jenis (dan juga lebih tinggi peringkat jenis). Ini dianggap sebagai fitur "eksperimental". Namun, komunitas kami telah berjuang untuk menemukan aplikasi praktis untuk fungsi ini, jadi ini pertanyaan terbuka apakah ini akan menjadi bagian "resmi" dari bahasa ini. Saya tidak berharap untuk dapat didukung oleh Java backend pada tahap tertentu.
Gavin King
-2

Siapa pun yang pernah diprogram dalam dBase akan tahu betapa bermanfaatnya makro baris tunggal untuk membuat kode yang dapat digunakan kembali. Meskipun saya belum memprogram dalam Lisp, saya telah membaca dari banyak orang lain yang bersumpah dengan menyusun waktu makro. Gagasan menyuntikkan kode ke dalam kode Anda pada waktu kompilasi digunakan dalam bentuk sederhana di setiap program C dengan arahan "include". Karena Lisp dapat melakukan ini dengan program Lisp dan karena Lisp sangat reflektif, Anda mendapatkan lebih banyak termasuk fleksibel.

Setiap programmer yang hanya akan mengambil string teks sewenang-wenang dari web dan meneruskannya ke database mereka bukan seorang programmer. Demikian juga, siapa pun yang memungkinkan data "pengguna" secara otomatis menjadi kode yang dapat dieksekusi jelas bodoh. Itu tidak berarti bahwa mengizinkan program untuk memanipulasi data pada waktu eksekusi dan kemudian menjalankannya sebagai kode adalah ide yang buruk. Saya percaya bahwa teknik ini akan sangat diperlukan di masa depan yang akan memiliki kode "pintar" yang sebenarnya menulis sebagian besar program. Seluruh "masalah data / kode" atau tidak adalah masalah keamanan dalam bahasa.

Salah satu masalah dengan sebagian besar bahasa adalah bahwa mereka dibuat untuk satu orang offline untuk menjalankan beberapa fungsi untuk diri mereka sendiri. Program dunia nyata mengharuskan banyak orang memiliki akses setiap saat dan pada saat yang sama dari banyak Core dan beberapa cluster komputer. Keamanan harus menjadi bagian dari bahasa daripada sistem operasi dan dalam waktu yang tidak terlalu lama itu akan menjadi.

David Clark
sumber
2
Selamat Datang di Programmer. Harap pertimbangkan untuk mengurangi retorika dalam jawaban Anda dan mendukung beberapa klaim Anda dengan referensi eksternal.
1
Setiap programmer yang akan memungkinkan data pengguna untuk secara otomatis menjadi kode yang dapat dieksekusi jelas - jelas bodoh . Tidak bodoh. Melakukannya dengan cara itu sering kali mudah dan jelas, dan jika mereka tidak tahu mengapa itu ide yang buruk dan bahwa ada solusi yang lebih baik, Anda tidak dapat menyalahkan mereka karena melakukannya. (Siapa pun yang terus melakukannya setelah mereka diajari bahwa ada cara yang lebih baik, jelas-jelas bodoh.)
Mason Wheeler