Saya sedang membaca artikel di sini: http://www.paulgraham.com/avg.html dan bagian tentang "blub paradox" sangat menarik. Sebagai seseorang yang sebagian besar kode dalam c ++ tetapi memiliki eksposur ke bahasa lain (kebanyakan Haskell) saya menyadari beberapa hal berguna dalam bahasa ini yang sulit untuk ditiru di c ++. Pertanyaannya adalah terutama untuk orang-orang yang mahir dalam c ++ dan beberapa bahasa lainnya, apakah ada fitur bahasa yang kuat atau idiom yang Anda gunakan dalam bahasa yang akan sulit untuk dikonseptualisasikan atau diimplementasikan jika Anda hanya menulis dalam c ++?
Secara khusus kutipan ini menarik perhatian saya:
Dengan induksi, satu-satunya programmer dalam posisi untuk melihat semua perbedaan kekuatan antara berbagai bahasa adalah mereka yang mengerti yang paling kuat. (Ini mungkin apa yang Eric Raymond maksudkan tentang Lisp menjadikan Anda seorang programmer yang lebih baik.) Anda tidak dapat mempercayai pendapat orang lain, karena paradoks Blub: mereka puas dengan bahasa apa pun yang mereka gunakan, karena itu menentukan cara mereka berpikir tentang program.
Jika ternyata saya setara dengan programmer "Blub" berdasarkan penggunaan c ++ ini menimbulkan pertanyaan berikut: Apakah ada konsep atau teknik yang berguna yang Anda temui dalam bahasa lain yang Anda akan merasa sulit dikonsep seandainya Anda telah menulis atau "berpikir" dalam c ++?
Sebagai contoh, paradigma pemrograman logika yang terlihat dalam bahasa seperti Prolog dan Mercury dapat diimplementasikan dalam c ++ menggunakan library castor tetapi pada akhirnya saya menemukan bahwa secara konseptual saya berpikir dalam hal kode Prolog dan menerjemahkan ke c ++ yang setara ketika menggunakan ini. Sebagai cara memperluas pengetahuan pemrograman saya, saya mencoba mencari tahu apakah ada contoh serupa lainnya dari idiom berguna / kuat yang lebih efisien diekspresikan dalam bahasa lain yang mungkin tidak saya sadari sebagai pengembang c ++. Contoh lain yang terlintas dalam pikiran adalah sistem makro di lisp, menghasilkan kode program dari dalam program tampaknya memiliki banyak manfaat untuk beberapa masalah. Ini tampaknya sulit untuk diimplementasikan dan dipikirkan dari dalam c ++.
Pertanyaan ini tidak dimaksudkan untuk menjadi debat "c ++ vs lisp" atau debat jenis perang bahasa apa pun. Mengajukan pertanyaan seperti ini adalah satu-satunya cara saya dapat melihat kemungkinan untuk mencari tahu tentang hal-hal yang saya tidak tahu saya tidak tahu.
sumber
there are things that other languages can do that Lisp can't
- Tidak mungkin, karena Lisp adalah Turing-complete. Mungkin Anda bermaksud mengatakan bahwa ada beberapa hal yang tidak praktis dilakukan di Lisp? Saya bisa mengatakan hal yang sama tentang bahasa pemrograman apa pun .Jawaban:
Ya, karena Anda menyebut Haskell:
Pencocokan Pola. Saya menemukan pencocokan pola menjadi lebih mudah untuk dibaca dan ditulis. Pertimbangkan definisi peta dan pikirkan bagaimana itu akan diterapkan dalam bahasa tanpa pencocokan pola.
Sistem tipe. Terkadang hal itu bisa menyakitkan, tetapi ini sangat membantu. Anda harus memprogram untuk benar-benar memahaminya dan berapa banyak bug yang ditangkap. Juga, transparansi referensial sangat bagus. Ini hanya menjadi jelas setelah pemrograman di Haskell untuk sementara waktu berapa banyak bug yang disebabkan oleh pengelolaan negara dalam bahasa imperatif.
Pemrograman fungsional secara umum. Menggunakan peta dan lipatan alih-alih iterasi. Rekursi. Ini tentang berpikir pada level yang lebih tinggi.
Evaluasi malas. Sekali lagi ini tentang berpikir di tingkat yang lebih tinggi dan membiarkan sistem menangani evaluasi.
Kabala, paket, dan modul. Memiliki paket unduhan Cabal bagi saya jauh lebih nyaman daripada menemukan kode sumber, menulis makefile, dll. Mampu mengimpor hanya nama-nama tertentu jauh lebih baik daripada pada dasarnya semua file sumber dibuang bersama kemudian dikompilasi.
sumber
Maybe
(untuk C ++ lihatstd::optional
), ini tentang harus secara eksplisit menandai hal-hal sebagai opsional / nullable / mungkin.Memoize!
Coba tulis dalam C ++. Tidak dengan C ++ 0x.
Terlalu rumit? Oke, coba dengan C ++ 0x.
Lihat apakah Anda dapat mengalahkan versi waktu kompilasi 4-line (atau 5-line, apa pun: P) ini dalam D:
Yang perlu Anda lakukan untuk memanggilnya adalah sesuatu seperti:
Anda juga dapat mencoba sesuatu yang serupa di Skema, meskipun sedikit lebih lambat karena terjadi pada saat run time dan karena pencarian di sini adalah linier, bukan hash (dan juga, karena Skema):
sumber
C ++ adalah bahasa multi-paradigma, yang artinya ia mencoba mendukung banyak cara berpikir. Kadang-kadang fitur C ++ lebih canggung atau kurang lancar daripada implementasi bahasa lain, seperti halnya dengan pemrograman fungsional.
Yang mengatakan, saya tidak bisa memikirkan bagian atas kepala saya dari fitur bahasa C ++ asli yang melakukan apa yang dilakukan
yield
dengan Python atau JavaScript.Contoh lain adalah pemrograman konkuren . C ++ 0x akan mengatakan tentang hal itu, tetapi standar saat ini tidak, dan konkurensi adalah cara berpikir yang sama sekali baru.
Juga, pengembangan yang cepat - bahkan pemrograman shell - adalah sesuatu yang tidak akan pernah Anda pelajari jika Anda tidak pernah meninggalkan domain pemrograman C ++.
sumber
setjmp
danlongjmp
. Saya tidak tahu berapa banyak yang rusak, tapi saya kira pengecualian akan menjadi yang pertama. Sekarang, jika Anda permisi, saya perlu membaca kembali Modern C ++ Design untuk mengeluarkannya dari kepala saya.Coroutine adalah fitur bahasa yang sangat berguna yang menopang banyak manfaat nyata dari bahasa lain selain C ++. Mereka pada dasarnya menyediakan tumpukan tambahan sehingga fungsi dapat terganggu dan dilanjutkan, menyediakan fasilitas seperti pipa ke bahasa yang dengan mudah memberi makan hasil operasi melalui filter ke operasi lain. Ini luar biasa, dan di Ruby saya menemukannya sangat intuitif dan elegan. Evaluasi malas terkait dengan ini juga.
Introspeksi dan kompilasi kode run-time / eksekusi / evaluasi / apa pun adalah fitur yang sangat kuat yang kurang C ++.
sumber
Setelah menerapkan sistem aljabar komputer dalam Lisp dan C ++, saya dapat memberi tahu Anda bahwa tugas itu jauh lebih mudah di Lisp, meskipun saya adalah pemula yang lengkap dalam bahasa ini. Sifat sederhana dari daftar semua ini menyederhanakan banyak algoritma. Memang, versi C ++ adalah zillions kali lebih cepat. Ya, saya bisa membuat versi lisp lebih cepat, tetapi kode tidak akan lispy. Scripting adalah hal lain yang selalu lebih mudah adalah lisp, misalnya. Ini semua tentang menggunakan alat yang tepat untuk pekerjaan itu.
sumber
Apa yang kita maksudkan ketika kita mengatakan bahwa satu bahasa "lebih kuat" daripada yang lain? Ketika kita mengatakan bahwa bahasa itu "ekspresif?" Atau "kaya?" Saya pikir kita maksudkan bahwa bahasa memperoleh kekuatan ketika bidang pandangnya cukup sempit untuk membuatnya mudah dan alami untuk menggambarkan masalah - benar-benar transisi negara, bukan? - yang hidup dalam pandangan itu. Namun bahasa itu sangat kurang kuat, kurang ekspresif, dan kurang bermanfaat ketika bidang pandang kita melebar.
Semakin "kuat" dan "ekspresif" bahasa, semakin terbatas penggunaannya. Jadi mungkin "kuat" dan "ekspresif" adalah kata-kata yang salah untuk digunakan untuk alat utilitas sempit. Mungkin "pantas" atau "abstrak" adalah kata-kata yang lebih baik untuk hal-hal seperti itu.
Saya mulai dalam pemrograman dengan menulis banyak hal tingkat rendah: driver perangkat dengan rutinitas interupsi mereka; program tertanam; kode sistem operasi. Kode itu intim dengan perangkat keras dan saya menulis semuanya dalam bahasa assembly. Kami tidak akan mengatakan assembler dalam bahasa yang paling abstrak, namun itu dan merupakan bahasa yang paling kuat dan ekspresif dari mereka semua. Saya dapat mengungkapkan masalah apa pun dalam bahasa assembly; itu sangat kuat sehingga saya bisa melakukan apa saja yang saya suka dengan mesin apa pun.
Dan semua pemahaman saya selanjutnya tentang bahasa tingkat yang lebih tinggi berhutang semuanya pada pengalaman saya dengan assembler. Semua yang saya pelajari kemudian mudah karena, Anda tahu, semuanya - tidak peduli seberapa abstrak - pada akhirnya harus menyesuaikan diri dengan perangkat keras.
Anda mungkin ingin melupakan tingkat abstraksi yang lebih tinggi dan lebih tinggi - yaitu bidang pandang yang semakin sempit. Anda selalu dapat mengambilnya nanti. Sangat mudah untuk belajar, dalam hitungan hari. Menurut pendapat saya, Anda akan lebih baik belajar bahasa perangkat keras 1 , untuk sedekat mungkin dengan tulang.
1 Mungkin tidak terlalu erat, tetapi
car
dancdr
mengambil nama mereka dari perangkat keras: Lisp pertama berjalan pada mesin yang memiliki Daftar Penurunan aktual dan Daftar Alamat aktual. Bagaimana dengan itu?sumber
Array Asosiatif
Cara khas memproses data adalah:
Alat yang tepat untuk itu adalah array asosiatif .
Saya tidak terlalu menyukai sintaksis asosiatif array JavaScript, karena saya tidak dapat membuat, katakanlah [x] [y] [z] = 8 , pertama-tama saya harus membuat [x] dan [x] [y] .
Oke, di C ++ (dan di Jawa) ada portofolio kelas wadah yang bagus, Peta , Multimap , apa pun, tetapi jika saya ingin memindai, saya harus membuat iterator, dan ketika saya ingin memasukkan elemen level-dalam yang baru, saya harus membuat semua level atas dll. Tidak nyaman.
Saya tidak mengatakan bahwa tidak ada array asosiatif yang dapat digunakan di C ++ (dan Java), tetapi bahasa skrip yang tidak diketik (atau diketik tidak ketat) mengalahkan yang dikompilasi, karena mereka adalah bahasa skrip yang tidak bertipe.
Penafian: Saya tidak terbiasa dengan C # dan bahasa .NET lainnya, AFAIK mereka memiliki penanganan array asosiatif yang baik.
sumber
dict
tipe bawaan (mis.x = {0: 5, 1: "foo", None: 500e3}
, Perhatikan bahwa tidak ada persyaratan untuk kunci atau nilai-nilai untuk jenis yang sama). Mencoba melakukan sesuatu sepertia[x][y][z] = 8
itu sulit karena bahasa harus melihat ke masa depan untuk melihat apakah Anda akan menetapkan nilai atau membuat level lain; ekspresia[x][y]
itu sendiri tidak memberi tahu Anda.Saya belajar Java, C \ C ++, Assembly, dan Java Script. Saya menggunakan C ++ untuk mencari nafkah.
Meskipun, saya harus mengatakan saya lebih suka pemrograman Majelis dan pemrograman C lebih. Ini sebagian besar sejalan dengan pemrograman Imperatif.
Saya tahu bahwa pemrograman Paradigma penting untuk mengkategorikan tipe data, dan memberikan konsep abstrak pemrograman yang lebih tinggi untuk memungkinkan pola desain yang kuat dan formalisasi kode. Meskipun dalam arti tertentu, setiap Paradigma adalah kumpulan pola dan koleksi untuk abstrak lapisan perangkat keras yang mendasarinya sehingga Anda tidak perlu memikirkan EAX, atau IP secara internal di dalam mesin.
Satu-satunya masalah saya dengan ini, adalah memungkinkan gagasan dan konsep masyarakat tentang cara kerja mesin diubah menjadi pernyataan Ideologi dan ambigu tentang apa yang sedang terjadi. Roti ini semua jenis abstraksi yang indah di atas abstrak untuk beberapa tujuan ideologi programmer.
Pada akhirnya, lebih baik memiliki pola pikir dan batasan yang jelas tentang apa CPU itu dan bagaimana komputer bekerja di bawah tenda. Semua CPU peduli tentang mengeksekusi serangkaian instruksi yang memindahkan data masuk dan keluar dari memori ke dalam register dan melakukan instruksi. Tidak memiliki konsep tipe data, atau konsep pemrograman yang lebih tinggi. Itu hanya memindahkan data.
Itu menjadi lebih kompleks ketika Anda menambahkan paradigma pemrograman ke dalam campuran karena pandangan kita tentang dunia semuanya berbeda.
sumber
C ++ membuat banyak pendekatan menjadi sulit. Saya akan mengatakan bahwa sebagian besar pemrograman sulit dikonsep jika Anda membatasi diri pada C ++. Berikut adalah beberapa contoh masalah yang lebih mudah dipecahkan dengan cara yang membuat C ++ sulit.
Registrasi alokasi dan konvensi panggilan
Banyak orang menganggap C ++ sebagai bahasa tingkat rendah bare metal tetapi sebenarnya tidak. Dengan mengabstraksikan detail-detail penting dari mesin, C ++ mempersulit untuk membuat konsep kepraktisan seperti alokasi register dan konvensi pemanggilan.
Untuk mempelajari tentang konsep-konsep seperti ini, saya sarankan untuk ikut program bahasa assembly dan lihat artikel ini tentang kualitas pembuatan kode ARM .
Pembuatan kode run-time
Jika Anda hanya tahu C ++ maka Anda mungkin berpikir bahwa templat adalah semua-dan-semua metaprogramming. Mereka bukan. Bahkan, mereka adalah alat yang buruk secara objektif untuk metaprogramming. Setiap program yang memanipulasi program lain adalah metaprogram, termasuk interpreter, kompiler, sistem aljabar komputer dan pembuktian teorema. Pembuatan kode run-time adalah fitur yang berguna untuk ini.
Saya merekomendasikan untuk memulai implementasi Skema dan bermain
EVAL
untuk belajar tentang evaluasi metacircular.Memanipulasi pohon
Pohon ada di mana-mana dalam pemrograman. Dalam parsing Anda memiliki pohon sintaksis abstrak. Dalam kompiler Anda memiliki IR yang merupakan pohon. Dalam pemrograman grafis dan GUI Anda memiliki pohon adegan.
Ini "Ridiculously Simple JSON Parser untuk C ++" beratnya hanya 484 LOC yang sangat kecil untuk C ++. Sekarang bandingkan dengan parser JSON sederhana saya sendiri yang beratnya hanya 60 LOC dari F #. Perbedaannya terutama karena tipe data aljabar ML dan pencocokan pola (termasuk pola aktif) membuatnya jauh lebih mudah untuk memanipulasi pohon.
Lihat juga pohon merah-hitam di OCaml .
Struktur data murni fungsional
Kurangnya GC di C ++ membuatnya praktis tidak mungkin untuk mengadopsi beberapa pendekatan yang bermanfaat. Struktur data yang murni fungsional adalah salah satu alat tersebut.
Misalnya, lihat pencocokan ekspresi reguler 47-baris ini di OCaml. Singkatnya ini sebagian besar disebabkan oleh penggunaan luas struktur data yang murni fungsional. Khususnya, penggunaan kamus dengan kunci yang ditetapkan. Itu sangat sulit dilakukan di C ++ karena kamus dan set stdlib semuanya bisa berubah tetapi Anda tidak bisa mengubah kunci kamus atau Anda merusak koleksi.
Pemrograman logika dan batalkan buffer adalah contoh praktis lainnya di mana struktur data yang murni fungsional membuat sesuatu yang sulit di C ++ sangat mudah dalam bahasa lain.
Buntut panggilan
Tidak hanya C + + tidak menjamin panggilan ekor tetapi RAII secara fundamental bertentangan dengan itu karena destruktor menghalangi jalannya panggilan di posisi ekor. Tail tail memungkinkan Anda membuat jumlah panggilan fungsi yang tidak terbatas hanya menggunakan jumlah ruang stack yang terbatas. Ini bagus untuk mengimplementasikan mesin negara, termasuk mesin negara yang dapat diperpanjang dan itu adalah kartu "keluar dari penjara" yang bagus dalam banyak keadaan yang canggung.
Misalnya, lihat penerapan masalah 0-1 ransel menggunakan gaya kelanjutan-lewat dengan memoisasi dalam F # dari industri keuangan. Ketika Anda memiliki panggilan ekor, gaya meneruskan kelanjutan bisa menjadi solusi yang jelas tetapi C ++ membuatnya tidak bisa diterapkan.
Konkurensi
Contoh nyata lainnya adalah pemrograman konkuren. Meskipun ini sepenuhnya mungkin dalam C ++ itu sangat rawan kesalahan dibandingkan dengan alat lain, terutama berkomunikasi proses berurutan seperti yang terlihat dalam bahasa seperti Erlang, Scala dan F #.
sumber
Ini adalah pertanyaan lama, tetapi karena tidak ada yang menyebutkannya, saya akan menambahkan daftar (dan sekarang dict) pemahaman. Sangat mudah untuk menulis satu-liner di Haskell atau Python yang memecahkan masalah Fizz-Buzz. Coba lakukan itu di C ++.
Sementara C ++ membuat gerakan besar ke modernitas dengan C ++ 11, itu sedikit sulit untuk menyebutnya bahasa "modern". C ++ 17 (yang belum dirilis) sedang membuat lebih banyak langkah untuk mencapai standar modern, selama "modern" berarti "bukan dari milenium sebelumnya".
Bahkan pemahaman yang paling sederhana yang dapat dituliskan dalam satu baris dalam Python (dan mematuhi batas panjang garis 79 karakter Guido) menjadi banyak dan banyak baris kode ketika diterjemahkan ke C ++, dan beberapa baris kode C ++ tersebut agak berbelit-belit.
sumber
Pustaka yang dikompilasi memanggil panggilan balik, yang merupakan fungsi anggota yang ditentukan pengguna dari kelas yang ditentukan pengguna.
Ini dimungkinkan di Objective-C, dan itu membuat pemrograman antarmuka pengguna mudah. Anda dapat memberi tahu tombol: "Tolong, panggil metode ini untuk objek ini ketika Anda ditekan", dan tombol akan melakukannya. Anda bebas menggunakan nama metode apa pun untuk panggilan balik yang Anda sukai, itu tidak dibekukan dalam kode perpustakaan, Anda tidak harus mewarisi dari adaptor agar berfungsi, juga kompilator tidak ingin menyelesaikan panggilan pada waktu kompilasi, dan, sama pentingnya, Anda dapat memberi tahu dua tombol untuk memanggil dua metode berbeda dari objek yang sama.
Saya belum melihat cara yang fleksibel untuk mendefinisikan callback dalam bahasa lain (meskipun saya akan sangat tertarik untuk mendengar tentang mereka!). Setara terdekat di C ++ mungkin melewati fungsi lambda yang melakukan panggilan yang diperlukan, yang lagi-lagi membatasi kode perpustakaan menjadi templat.
Fitur Objective-C inilah yang telah mengajarkan saya untuk menghargai kemampuan suatu bahasa untuk melewatkan semua jenis objek / fungsi / apa pun-konsep-penting-bahasa-yang terkandung di sekitar secara bebas, bersama dengan kekuatan untuk menyimpannya ke variabel. Setiap titik dalam bahasa yang mendefinisikan semua jenis konsep, tetapi tidak menyediakan sarana untuk menyimpannya (atau referensi untuk itu) di semua jenis variabel yang tersedia, adalah batu sandungan yang signifikan, dan kemungkinan sumber yang jauh lebih buruk, kode duplikat. Sayangnya, bahasa pemrograman barok cenderung menunjukkan sejumlah poin ini:
Dalam C ++ Anda tidak bisa menuliskan jenis VLA, atau menyimpan pointer ke sana. Ini secara efektif melarang array multidimensi sebenarnya dari ukuran dinamis (yang tersedia di C sejak C99).
Dalam C ++ Anda tidak dapat menuliskan jenis lambda. Anda bahkan tidak bisa mengetikkannya. Dengan demikian, tidak ada cara untuk menyebarkan lambda, atau menyimpan referensi untuk suatu objek. Fungsi Lambda hanya dapat diteruskan ke templat.
Di Fortran Anda tidak dapat menuliskan jenis daftar nama. Tidak ada cara untuk mengirimkan daftar nama ke segala jenis rutinitas. Jadi, jika Anda memiliki algoritme kompleks yang harus dapat menangani dua daftar nama yang berbeda, Anda kurang beruntung. Anda tidak bisa hanya menulis algoritme satu kali dan meneruskan daftar nama yang relevan kepadanya.
Ini hanya beberapa contoh, tetapi Anda melihat titik yang sama: Setiap kali Anda melihat pembatasan seperti itu untuk pertama kalinya, Anda biasanya tidak akan peduli karena tampaknya ide gila untuk melakukan hal terlarang. Namun, ketika Anda melakukan pemrograman yang sungguh-sungguh dalam bahasa itu, Anda akhirnya sampai pada titik di mana pembatasan yang tepat ini menjadi gangguan nyata.
sumber
I have not seen a similarly flexible way to define a callback in any other language yet (though I'd be very interested to hear about them!)
Apa yang baru saja Anda deskripsikan terdengar persis seperti cara kerja kode UI berbasis-acara di Delphi. (Dan dalam. WinForms NET, yang sangat dipengaruhi oleh Delphi.)std::vector
. Meskipun sedikit kurang efisien karena tidak menggunakan alokasi tumpukan, secara fungsional isomorfik untuk VLA, jadi tidak benar-benar dianggap sebagai masalah tipe "blub": Pemrogram C ++ dapat melihat cara kerjanya dan hanya berkata, "ah ya , C melakukan itu lebih efisien daripada C ++ ".std::function
.object::method
dan itu akan diubah menjadi sebuah instance antarmuka apa pun yang diharapkan oleh kode penerima. C # memiliki delegasi. Setiap bahasa objek-fungsional memiliki fitur ini karena pada dasarnya titik penampang kedua paradigma.