Ada beberapa masalah penting yang saya pikir tidak terjawab oleh semua jawaban yang ada.
Mengetik lemah berarti memungkinkan akses ke representasi yang mendasarinya. Di C, saya bisa membuat pointer ke karakter, kemudian memberi tahu kompiler saya ingin menggunakannya sebagai pointer ke integer:
char sz[] = "abcdefg";
int *i = (int *)sz;
Pada platform little-endian dengan bilangan bulat 32-bit, ini i
menjadi array angka 0x64636261
dan 0x00676665
. Bahkan, Anda bahkan dapat mengarahkan pointer sendiri ke integer (dengan ukuran yang sesuai):
intptr_t i = (intptr_t)&sz;
Dan tentu saja ini berarti saya dapat menimpa memori di mana saja dalam sistem. *
char *spam = (char *)0x12345678
spam[0] = 0;
* Tentu saja OS modern menggunakan memori virtual dan perlindungan halaman jadi saya hanya dapat menimpa memori proses saya sendiri, tetapi tidak ada apa pun tentang C itu sendiri yang menawarkan perlindungan seperti itu, seperti yang pernah dikodekan oleh siapa pun, katakanlah, Classic Mac OS atau Win16 dapat memberitahu Anda.
Lisp tradisional memungkinkan jenis peretasan yang serupa; pada beberapa platform, mengapung kata ganda dan sel kontra adalah tipe yang sama, dan Anda bisa meneruskan satu ke fungsi yang mengharapkan yang lain dan itu akan "bekerja".
Sebagian besar bahasa saat ini tidak cukup lemah seperti C dan Lisp, tetapi banyak dari mereka masih agak bocor. Misalnya, bahasa OO apa pun yang memiliki "downcast" yang tidak dicentang, * itu tipe kebocoran: Anda pada dasarnya memberi tahu kompiler "Saya tahu saya tidak memberi Anda informasi yang cukup untuk mengetahui ini aman, tapi saya cukup yakin itu adalah, "ketika seluruh titik dari sistem tipe adalah bahwa kompiler selalu memiliki informasi yang cukup untuk mengetahui apa yang aman.
* Downcast yang dicentang tidak membuat sistem tipe bahasa menjadi lebih lemah hanya karena ia menggerakkan check ke runtime. Jika ya, maka subtipe polimorfisme (alias panggilan fungsi virtual atau sepenuhnya dinamis) akan menjadi pelanggaran yang sama pada sistem tipe, dan saya tidak berpikir ada yang mau mengatakan itu.
Sangat sedikit bahasa "scripting" yang lemah dalam hal ini. Bahkan dalam Perl atau Tcl, Anda tidak dapat mengambil string dan hanya menafsirkan byte-nya sebagai integer. * Tetapi perlu dicatat bahwa dalam CPython (dan juga untuk banyak penerjemah lain untuk banyak bahasa), jika Anda benar-benar gigih, Anda dapat digunakan ctypes
untuk memuat libpython
, membuang objek id
ke POINTER(Py_Object)
, dan memaksa sistem tipe bocor. Apakah ini membuat sistem tipe lemah atau tidak tergantung pada kasus penggunaan Anda — jika Anda mencoba menerapkan sandbox eksekusi terbatas dalam bahasa untuk memastikan keamanan, Anda harus berurusan dengan pelarian semacam ini ...
* Anda dapat menggunakan fungsi seperti struct.unpack
membaca byte dan membangun int baru dari "bagaimana C akan mewakili byte ini", tapi itu jelas tidak bocor; bahkan Haskell memungkinkan itu.
Sementara itu, konversi implisit benar-benar berbeda dari sistem tipe lemah atau bocor.
Setiap bahasa, bahkan Haskell, memiliki fungsi untuk, misalnya, mengubah integer menjadi string atau float. Tetapi beberapa bahasa akan melakukan beberapa konversi untuk Anda secara otomatis — misalnya, dalam C, jika Anda memanggil fungsi yang menginginkan float
, dan Anda meneruskannya int
, konversi akan dilakukan untuk Anda. Ini pasti dapat menyebabkan bug dengan, misalnya, luapan yang tidak terduga, tetapi mereka bukan jenis bug yang sama dengan yang Anda dapatkan dari sistem tipe lemah. Dan C tidak benar-benar menjadi lebih lemah di sini; Anda dapat menambahkan int dan float di Haskell, atau bahkan menggabungkan float ke string, Anda hanya perlu melakukannya secara lebih eksplisit.
Dan dengan bahasa yang dinamis, ini cukup suram. Tidak ada yang namanya "fungsi yang menginginkan pelampung" dengan Python atau Perl. Tetapi ada fungsi kelebihan beban yang melakukan hal-hal yang berbeda dengan tipe yang berbeda, dan ada perasaan intuitif yang kuat bahwa, misalnya, menambahkan string ke sesuatu yang lain adalah "fungsi yang menginginkan string". Dalam arti itu, Perl, Tcl, dan JavaScript tampaknya melakukan banyak konversi implisit ( "a" + 1
memberi Anda "a1"
), sementara Python melakukan lebih sedikit ( "a" + 1
menimbulkan pengecualian, tetapi 1.0 + 1
memberi Anda 2.0
*). Sulit untuk memahami hal itu dalam istilah formal — mengapa tidak ada +
yang mengambil string dan int, ketika jelas ada fungsi lain, seperti pengindeksan, yang bisa?
* Sebenarnya, dalam Python modern, itu dapat dijelaskan dalam hal subtyping OO, karena isinstance(2, numbers.Real)
itu benar. Saya tidak berpikir ada arti di mana 2
merupakan turunan dari tipe string di Perl atau JavaScript ... meskipun di Tcl, sebenarnya, karena semuanya adalah turunan dari string.
Akhirnya, ada definisi lain, sepenuhnya ortogonal, dari pengetikan "kuat" vs. "lemah", di mana "kuat" berarti kuat / fleksibel / ekspresif.
Misalnya, Haskell memungkinkan Anda menentukan tipe yang merupakan angka, string, daftar tipe ini, atau peta dari string ke tipe ini, yang merupakan cara sempurna untuk mewakili apa pun yang dapat diterjemahkan dari JSON. Tidak ada cara untuk mendefinisikan tipe seperti itu di Java. Tetapi setidaknya Java memiliki tipe parametrik (generik), jadi Anda dapat menulis fungsi yang mengambil Daftar T dan mengetahui bahwa elemen-elemennya adalah tipe T; bahasa lain, seperti Java awal, memaksa Anda untuk menggunakan Daftar Objek dan tertunduk. Tetapi setidaknya Java memungkinkan Anda membuat tipe baru dengan metode mereka sendiri; C hanya memungkinkan Anda membuat struktur. Dan BCPL bahkan tidak memilikinya. Dan seterusnya ke perakitan, di mana satu-satunya jenis panjang bit yang berbeda.
Jadi, dalam hal itu, sistem tipe Haskell lebih kuat dari Jawa modern, yang lebih kuat dari Jawa sebelumnya, yang lebih kuat dari C, yang lebih kuat dari BCPL.
Jadi, di mana Python cocok dengan spektrum itu? Itu agak rumit. Dalam banyak kasus, mengetik bebek memungkinkan Anda untuk mensimulasikan semua yang dapat Anda lakukan di Haskell, dan bahkan beberapa hal yang tidak dapat Anda lakukan; yakin, kesalahan ditangkap saat runtime alih-alih waktu kompilasi, tetapi masih tertangkap. Namun, ada beberapa kasus di mana mengetik bebek tidak cukup. Misalnya, di Haskell, Anda dapat mengatakan bahwa daftar int kosong adalah daftar int, sehingga Anda dapat memutuskan bahwa pengurangan +
atas daftar tersebut harus mengembalikan 0 *; dalam Python, daftar kosong adalah daftar kosong; tidak ada jenis informasi untuk membantu Anda memutuskan pengurangan apa yang +
harus dilakukan.
* Sebenarnya, Haskell tidak membiarkan Anda melakukan ini; jika Anda memanggil fungsi pengurangan yang tidak mengambil nilai awal pada daftar kosong, Anda mendapatkan kesalahan. Tetapi sistem tipenya cukup kuat sehingga Anda bisa membuatnya bekerja, dan Python tidak.