Saya telah menggunakan python selama beberapa hari sekarang dan saya pikir saya mengerti perbedaan antara pengetikan dinamis dan statis. Apa yang saya tidak mengerti adalah dalam keadaan apa itu akan lebih disukai. Ini fleksibel dan mudah dibaca, tetapi dengan mengorbankan lebih banyak pemeriksaan runtime dan pengujian unit tambahan yang diperlukan.
Selain kriteria non-fungsional seperti fleksibilitas dan keterbacaan, alasan apa yang ada untuk memilih pengetikan dinamis? Apa yang bisa saya lakukan dengan pengetikan dinamis yang tidak dimungkinkan? Contoh kode spesifik apa yang dapat Anda pikirkan yang menggambarkan keuntungan nyata dari pengetikan dinamis?
dynamic-typing
static-typing
Justin984
sumber
sumber
Jawaban:
Karena Anda meminta contoh spesifik, saya akan memberikan satu.
ORM besar - besaran Rob Conery adalah 400 baris kode. Itu kecil karena Rob mampu memetakan tabel SQL dan memberikan hasil objek tanpa memerlukan banyak jenis statis untuk mencerminkan tabel SQL. Ini dilakukan dengan menggunakan
dynamic
tipe data dalam C #. Halaman web Rob menjelaskan proses ini secara terperinci, tetapi tampak jelas bahwa, dalam kasus penggunaan khusus ini, pengetikan dinamis sebagian besar bertanggung jawab atas singkatnya kode.Bandingkan dengan Dapper Sam Saffron , yang menggunakan tipe statis; yang
SQLMapper
kelas saja 3000 baris kode.Perhatikan bahwa sangkalan yang biasa berlaku, dan jarak tempuh Anda dapat bervariasi; Dapper memiliki tujuan yang berbeda dengan Massive. Saya hanya menunjukkan ini sebagai contoh dari sesuatu yang dapat Anda lakukan dalam 400 baris kode yang mungkin tidak akan mungkin tanpa pengetikan dinamis.
Pengetikan dinamis memungkinkan Anda untuk menunda keputusan mengetik untuk runtime. Itu saja.
Apakah Anda menggunakan bahasa yang diketik secara dinamis atau yang diketik secara statis, pilihan jenis Anda harus tetap masuk akal. Anda tidak akan menambahkan dua string bersama dan mengharapkan jawaban numerik kecuali string berisi data numerik, dan jika tidak, Anda akan mendapatkan hasil yang tidak terduga. Bahasa yang diketik secara statis tidak akan membiarkan Anda melakukan ini sejak awal.
Para pendukung jenis bahasa statis menunjukkan bahwa kompiler dapat melakukan sejumlah besar "kewarasan memeriksa" kode Anda pada waktu kompilasi, sebelum satu baris dieksekusi. Ini adalah Good Thing ™.
C # memiliki
dynamic
kata kunci, yang memungkinkan Anda untuk menunda keputusan tipe untuk runtime tanpa kehilangan manfaat keamanan tipe statis di sisa kode Anda. Ketik inferensi (var
) menghilangkan banyak rasa sakit menulis dalam bahasa yang diketik secara statis dengan menghilangkan kebutuhan untuk selalu secara eksplisit menyatakan jenis.Bahasa dinamis tampaknya lebih menyukai pendekatan yang lebih interaktif dan langsung untuk pemrograman. Tidak ada yang mengharapkan Anda harus menulis kelas dan melalui siklus kompilasi untuk mengetikkan sedikit kode Lisp dan melihatnya mengeksekusi. Namun itulah yang saya harapkan dilakukan di C #.
sumber
Frasa seperti "pengetikan statis" dan "pengetikan dinamis" banyak digunakan, dan orang-orang cenderung menggunakan definisi yang agak berbeda, jadi mari kita mulai dengan mengklarifikasi apa yang kita maksudkan.
Pertimbangkan bahasa yang memiliki tipe statis yang diperiksa pada waktu kompilasi. Tetapi katakan bahwa kesalahan ketik hanya menghasilkan peringatan non-fatal, dan pada saat runtime, semuanya diketik bebek. Tipe statis ini hanya untuk kenyamanan programmer, dan tidak mempengaruhi codegen. Ini menggambarkan bahwa pengetikan statis tidak dengan sendirinya memaksakan batasan apa pun, dan tidak saling eksklusif dengan pengetikan dinamis. (Objective-C sangat banyak seperti ini.)
Tetapi kebanyakan sistem tipe statis tidak berperilaku seperti ini. Ada dua sifat umum dari sistem tipe statis yang dapat memaksakan batasan:
Kompiler dapat menolak program yang berisi kesalahan tipe statis.
Ini adalah batasan karena banyak program aman jenis tentu mengandung kesalahan jenis statis.
Sebagai contoh, saya memiliki skrip Python yang perlu dijalankan karena Python 2 dan Python 3. Beberapa fungsi mengubah tipe parameter mereka antara Python 2 dan 3, jadi saya punya kode seperti ini:
Pemeriksa tipe statis Python 2 akan menolak kode Python 3 (dan sebaliknya), meskipun itu tidak akan pernah dieksekusi. Program safe type saya mengandung kesalahan tipe statis.
Sebagai contoh lain, pertimbangkan program Mac yang ingin dijalankan pada OS X 10.6, tetapi manfaatkan fitur-fitur baru di 10.7. Metode 10.7 mungkin ada atau tidak ada saat runtime, dan ada pada saya, programmer, untuk mendeteksinya. Pemeriksa tipe statis dipaksa untuk menolak program saya untuk memastikan keamanan jenis, atau menerima program, bersama dengan kemungkinan menghasilkan kesalahan jenis (fungsi hilang) saat runtime.
Pemeriksaan tipe statis mengasumsikan bahwa lingkungan runtime dijelaskan dengan memadai oleh informasi waktu kompilasi. Tetapi memprediksi masa depan itu berbahaya!
Berikut ini satu batasan lagi:
Compiler dapat menghasilkan kode yang menganggap tipe runtime adalah tipe statis.
Dengan asumsi tipe statis "benar" memberikan banyak peluang untuk optimasi, tetapi optimasi ini dapat membatasi. Contoh yang baik adalah objek proxy, misalnya remoting. Katakanlah Anda ingin memiliki objek proxy lokal yang meneruskan pemanggilan metode ke objek nyata dalam proses lain. Akan lebih baik jika proxy itu generik (sehingga dapat menyamar sebagai objek apa pun) dan transparan (sehingga kode yang ada tidak perlu tahu itu berbicara ke proxy). Tetapi untuk melakukan ini, kompiler tidak dapat menghasilkan kode yang menganggap tipe statis sudah benar, misalnya dengan pemanggilan metode inlining secara statis, karena itu akan gagal jika objek sebenarnya adalah proxy.
Contoh dari tindakan remoting seperti itu termasuk NSXPCConnection milik ObjC atau TransparentProxy C # (yang implementasinya membutuhkan beberapa pesimisasi dalam runtime - lihat di sini untuk diskusi).
Ketika codegen tidak bergantung pada tipe statis, dan Anda memiliki fasilitas seperti penerusan pesan, Anda dapat melakukan banyak hal keren dengan objek proxy, debugging, dll.
Jadi itu adalah contoh dari beberapa hal yang dapat Anda lakukan jika Anda tidak diharuskan untuk memuaskan pemeriksa tipe. Batasan tidak dikenakan oleh tipe statis, tetapi oleh pemeriksaan tipe statis yang dipaksakan.
sumber
A Python 2 static type checker would reject the Python 3 code (and vice versa), even though it would never be executed. My type safe program contains a static type error.
Dalam bahasa statis apa pun yang masuk akal, Anda bisa melakukan ini denganIFDEF
pernyataan preprocessor tipe, sambil menjaga keamanan tipe dalam kedua kasus.Variabel tipe-bebek adalah hal pertama yang dipikirkan semua orang, tetapi dalam kebanyakan kasus Anda bisa mendapatkan manfaat yang sama melalui inferensi tipe statis.
Tetapi mengetik bebek dalam koleksi yang dibuat secara dinamis sulit dicapai dengan cara lain:
Jadi, tipe apa yang
JSON.parse
kembali? Kamus array-of-integer-atau-kamus-of-string? Tidak, bahkan itu tidak cukup umum.JSON.parse
harus mengembalikan semacam "nilai varian" yang dapat berupa null, bool, float, string, array dari semua jenis ini secara rekursif, atau kamus dari string ke salah satu dari jenis ini secara rekursif. Kekuatan utama dari pengetikan dinamis berasal dari jenis varian yang demikian.Sejauh ini, ini adalah manfaat dari tipe dinamis , bukan bahasa yang diketik secara dinamis. Bahasa statis yang layak dapat mensimulasikan jenis seperti itu dengan sempurna. (Dan bahkan bahasa "buruk" sering dapat mensimulasikan mereka dengan memecah keselamatan tipe di bawah tenda dan / atau membutuhkan sintaks akses yang canggung.)
Keuntungan dari bahasa yang diketik secara dinamis adalah bahwa tipe seperti itu tidak dapat disimpulkan oleh sistem inferensi tipe statis. Anda harus menulis jenisnya secara eksplisit. Tetapi dalam banyak kasus seperti itu — termasuk sekali ini — kode untuk menggambarkan tipe persis sama rumitnya dengan kode untuk mem-parsing / membangun objek tanpa mendeskripsikan jenisnya, sehingga masih belum tentu merupakan keuntungan.
sumber
Karena setiap jenis sistem statis praktis jarak jauh sangat terbatas dibandingkan dengan bahasa pemrograman yang bersangkutan, ia tidak dapat mengekspresikan semua invarian yang dapat diperiksa kode saat runtime. Agar tidak mengelak dari jaminan yang mencoba diberikan oleh sistem tipe, maka sistem itu memilih untuk bersikap konservatif dan melarang kasus penggunaan yang lulus pemeriksaan ini, tetapi tidak dapat (dalam sistem tipe) terbukti.
Saya akan membuat contoh. Misalkan Anda menerapkan model data sederhana untuk menggambarkan objek data, koleksi mereka, dll. Yang diketik secara statis dalam arti bahwa, jika model mengatakan atribut
x
objek tipe Foo memegang integer, itu harus selalu memegang integer. Karena ini adalah konstruksi runtime, Anda tidak dapat mengetiknya secara statis. Misalkan Anda menyimpan data yang dijelaskan dalam file YAML. Anda membuat peta hash (untuk diserahkan ke perpustakaan YAML nanti), dapatkanx
atribut, simpan di peta, dapatkan atribut lainnya yang kebetulan berupa string, ... tahan sebentar? Apa jenisthe_map[some_key]
sekarang? Nah menembak, kita tahu bahwasome_key
adalah'x'
dan hasil tersebut maka harus integer, tapi sistem jenis bahkan tidak bisa mulai untuk alasan tentang ini.Beberapa jenis sistem yang diteliti secara aktif dapat bekerja untuk contoh spesifik ini, tetapi ini sangat rumit (baik untuk penulis kompiler untuk mengimplementasikan dan untuk programmer untuk alasan), terutama untuk sesuatu yang "sederhana" ini (maksud saya, saya hanya menjelaskannya dalam satu ayat).
Tentu saja, solusi hari ini adalah tinju segalanya dan kemudian casting (atau memiliki banyak metode override, yang sebagian besar menimbulkan pengecualian "tidak diterapkan"). Tapi ini tidak diketik secara statis, ini adalah hack di sekitar sistem tipe untuk melakukan pemeriksaan tipe saat runtime.
sumber
Tidak ada yang dapat Anda lakukan dengan pengetikan dinamis yang tidak dapat Anda lakukan dengan pengetikan statis, karena Anda dapat menerapkan pengetikan dinamis di atas bahasa yang diketik secara statis.
Contoh singkat di Haskell:
Dengan cukup case Anda dapat menerapkan sistem tipe dinamis apa pun yang diberikan.
Sebaliknya, Anda juga dapat menerjemahkan program yang diketik secara statis menjadi program dinamis yang setara. Tentu saja, Anda akan kehilangan semua jaminan waktu kompilasi kebenaran yang disediakan oleh bahasa yang diketik secara statis.
Sunting: Saya ingin menjaga ini tetap sederhana, tetapi di sini ada lebih banyak detail tentang model objek
Fungsi mengambil daftar data sebagai argumen dan melakukan perhitungan dengan efek samping di ImplMonad, dan mengembalikan data.
DMember
adalah nilai anggota atau fungsi.Perluas
Data
untuk menyertakan Objek dan Fungsi. Objek adalah daftar anggota yang disebutkan.Tipe statis ini cukup untuk mengimplementasikan setiap sistem objek yang diketik secara dinamis yang saya kenal.
sumber
Data
.+
operator baru yang menggabungkan duaData
nilai menjadi nilai lainData
.Data
mewakili nilai standar dalam sistem tipe dinamis.Membran :
Setiap jenis dibungkus oleh jenis yang memiliki antarmuka yang sama, tetapi yang memotong pesan dan membungkus dan membuka nilai saat mereka melintasi membran. Apa jenis fungsi bungkus dalam bahasa yang diketik secara statis favorit Anda? Mungkin Haskell memiliki tipe untuk fungsi-fungsi itu, tetapi sebagian besar bahasa yang diketik secara statis tidak atau mereka akhirnya menggunakan Object → Object, secara efektif melepaskan tanggung jawab mereka sebagai pemeriksa tipe.
sumber
class Foo a where ...
data Wrapper = forall a. Foo a => Wrapper a
String
karena ini adalah jenis beton di Jawa. Smalltalk tidak memiliki masalah ini karena tidak mencoba mengetik#doesNotUnderstand
.Seperti yang disebutkan seseorang, secara teori tidak banyak yang dapat Anda lakukan dengan pengetikan dinamis yang tidak dapat Anda lakukan dengan pengetikan statis jika Anda ingin menerapkan mekanisme tertentu sendiri. Sebagian besar bahasa menyediakan mekanisme relaksasi jenis untuk mendukung fleksibilitas jenis seperti pointer kosong, dan root Object object atau antarmuka kosong.
Pertanyaan yang lebih baik adalah mengapa pengetikan dinamis lebih cocok dan lebih tepat dalam situasi dan masalah tertentu.
Pertama, mari kita definisikan
Entitas - Saya akan memerlukan gagasan umum tentang beberapa entitas dalam kode. Itu bisa berupa apa saja dari bilangan primitif hingga data kompleks.
Perilaku - katakanlah entitas kita memiliki beberapa keadaan dan serangkaian metode yang memungkinkan dunia luar untuk menginstruksikan entitas untuk reaksi tertentu. Mari kita memanggil antarmuka negara + dari entitas ini perilakunya. Satu entitas dapat memiliki lebih dari satu perilaku yang digabungkan dengan cara tertentu oleh alat yang disediakan oleh bahasa.
Definisi entitas dan perilakunya - setiap bahasa menyediakan beberapa cara abstraksi yang membantu Anda mendefinisikan perilaku (serangkaian metode + keadaan internal) entitas tertentu dalam program. Anda dapat menetapkan nama untuk perilaku ini dan mengatakan bahwa semua instance yang memiliki perilaku ini adalah tipe tertentu .
Ini mungkin sesuatu yang tidak biasa. Dan seperti yang Anda katakan, Anda memahami perbedaannya, tetapi tetap saja. Mungkin penjelasan yang tidak lengkap dan paling akurat tapi saya harap cukup menyenangkan untuk membawa nilai :)
Pengetikan statis - perilaku semua entitas dalam program Anda diperiksa dalam waktu kompilasi, sebelum kode mulai dijalankan. Ini berarti bahwa jika Anda ingin misalnya entitas tipe Person Anda memiliki perilaku (berperilaku seperti) Magician maka Anda harus mendefinisikan entitas MagicianPerson dan memberikannya perilaku seorang magician seperti throwMagic (). Jika Anda berada dalam kode Anda, salah kirim ke kompiler Person.throwMagic () biasa akan memberi tahu Anda
"Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".
Pengetikan dinamis - di lingkungan pengetikan dinamis, perilaku entitas yang tersedia tidak dicentang hingga Anda benar-benar mencoba melakukan sesuatu dengan entitas tertentu. Menjalankan kode Ruby yang meminta Person.throwMagic () tidak akan ditangkap sampai kode Anda benar-benar datang ke sana. Ini terdengar frustasi, bukan. Tapi itu kedengarannya menyenangkan juga. Berdasarkan pada properti ini Anda dapat melakukan hal-hal menarik. Seperti, katakanlah Anda merancang game di mana segala sesuatu dapat berubah menjadi Magician dan Anda tidak benar-benar tahu siapa itu, sampai Anda tiba pada titik tertentu dalam kode. Dan kemudian Frog datang dan kamu berkata
HeyYouConcreteInstanceOfFrog.include Magic
dan sejak saat itu Katak ini menjadi satu Katak tertentu yang memiliki kekuatan Sihir. Katak lain, masih belum. Anda lihat, dalam bahasa pengetikan statis, Anda harus mendefinisikan hubungan ini dengan rata-rata standar kombinasi perilaku (seperti implementasi antarmuka). Dalam bahasa pengetikan dinamis, Anda bisa melakukannya di runtime dan tidak ada yang peduli.Sebagian besar bahasa pengetikan dinamis memiliki mekanisme untuk menyediakan perilaku umum yang akan menangkap pesan apa pun yang diteruskan ke antarmuka mereka. Misalnya Ruby
method_missing
dan PHP__call
kalau saya ingat bagus. Itu berarti bahwa Anda dapat melakukan segala hal menarik dalam menjalankan waktu program dan membuat keputusan tipe berdasarkan status program saat ini. Ini membawa alat untuk pemodelan masalah yang jauh lebih fleksibel daripada di, katakanlah, bahasa pemrograman statis konservatif seperti Java.sumber