Có một số vấn đề quan trọng mà tôi nghĩ rằng tất cả các câu trả lời hiện có đã bỏ lỡ.
Gõ yếu có nghĩa là cho phép truy cập vào các đại diện cơ bản. Trong C, tôi có thể tạo một con trỏ tới các ký tự, sau đó báo cho trình biên dịch tôi muốn sử dụng nó làm con trỏ tới các số nguyên:
char sz[] = "abcdefg";
int *i = (int *)sz;
Trên nền tảng endian nhỏ với số nguyên 32 bit, điều này tạo i
thành một mảng các số 0x64636261
và 0x00676665
. Trong thực tế, bạn thậm chí có thể tự chuyển con trỏ tới số nguyên (có kích thước phù hợp):
intptr_t i = (intptr_t)&sz;
Và tất nhiên điều này có nghĩa là tôi có thể ghi đè lên bộ nhớ ở bất cứ đâu trong hệ thống. *
char *spam = (char *)0x12345678
spam[0] = 0;
* Tất nhiên hệ điều hành hiện đại sử dụng bộ nhớ ảo và bảo vệ trang để tôi chỉ có thể ghi đè lên bộ nhớ của chính mình, nhưng không có gì về chính C cung cấp sự bảo vệ như vậy, như bất kỳ ai từng mã hóa, nói, Classic Mac OS hoặc Win16 đều có thể cho bạn biết.
Lisp truyền thống cho phép các loại tin tặc tương tự; trên một số nền tảng, các ô nổi hai từ và các ô khuyết điểm là cùng loại và bạn có thể chuyển một hàm này sang một hàm khác và nó sẽ "hoạt động".
Hầu hết các ngôn ngữ ngày nay không quá yếu như C và Lisp, nhưng nhiều ngôn ngữ vẫn còn hơi rò rỉ. Ví dụ: bất kỳ ngôn ngữ OO nào có "downcast" không được kiểm tra, * đó là một loại rò rỉ: về cơ bản bạn đang nói với trình biên dịch "Tôi biết tôi không cung cấp cho bạn đủ thông tin để biết điều này an toàn, nhưng tôi khá chắc chắn đó là, "khi toàn bộ quan điểm của một hệ thống kiểu là trình biên dịch luôn có đủ thông tin để biết những gì an toàn.
* Một downcast được kiểm tra không làm cho hệ thống loại ngôn ngữ trở nên yếu hơn chỉ vì nó chuyển kiểm tra sang thời gian chạy. Nếu đúng như vậy, thì tính đa hình của kiểu con (còn gọi là các hàm gọi ảo hoặc hoàn toàn động) sẽ là vi phạm tương tự hệ thống loại và tôi không nghĩ có ai muốn nói điều đó.
Rất ít ngôn ngữ "scripting" yếu theo nghĩa này. Ngay cả trong Perl hoặc Tcl, bạn không thể lấy một chuỗi và chỉ diễn giải các byte của nó là một số nguyên. * Nhưng đáng chú ý là trong CPython (và tương tự đối với nhiều trình thông dịch khác cho nhiều ngôn ngữ), nếu bạn thực sự kiên trì, bạn có thể sử dụng ctypes
để tải lên libpython
, truyền vật thể id
lên a POINTER(Py_Object)
và buộc hệ thống loại bị rò rỉ. Việc này có làm cho hệ thống loại yếu hay không phụ thuộc vào các trường hợp sử dụng của bạn, nếu bạn đang cố gắng thực hiện một hộp cát thực thi bị hạn chế bằng ngôn ngữ để đảm bảo an ninh, bạn phải đối phó với các loại thoát này.
* Bạn có thể sử dụng một hàm như struct.unpack
để đọc các byte và xây dựng một int mới từ "cách C sẽ biểu diễn các byte này", nhưng rõ ràng điều đó không bị rò rỉ; thậm chí Haskell cho phép điều đó.
Trong khi đó, chuyển đổi ngầm thực sự là một điều khác biệt với một hệ thống loại yếu hoặc rò rỉ.
Mọi ngôn ngữ, thậm chí Haskell, đều có chức năng chuyển đổi một số nguyên thành chuỗi hoặc dấu phẩy. Nhưng một số ngôn ngữ sẽ tự động thực hiện một số chuyển đổi đó cho bạn, ví dụ như trong C, nếu bạn gọi một hàm muốn a float
và bạn chuyển nó vào int
, nó sẽ được chuyển đổi cho bạn. Điều này chắc chắn có thể dẫn đến các lỗi với, ví dụ, tràn bất ngờ, nhưng chúng không phải là các loại lỗi bạn gặp phải từ một hệ thống loại yếu. Và C không thực sự yếu hơn ở đây; bạn có thể thêm một int và một float trong Haskell, hoặc thậm chí ghép một float vào một chuỗi, bạn chỉ cần làm điều đó rõ ràng hơn.
Và với các ngôn ngữ năng động, điều này khá âm u. Không có thứ gọi là "một hàm muốn nổi" trong Python hoặc Perl. Nhưng có những hàm quá tải làm những việc khác nhau với các loại khác nhau và có một cảm giác trực quan mạnh mẽ, ví dụ, thêm một chuỗi vào một thứ khác là "một hàm muốn một chuỗi". Theo nghĩa đó, Perl, Tcl và JavaScript dường như thực hiện rất nhiều chuyển đổi ngầm định ( "a" + 1
mang lại cho bạn "a1"
), trong khi Python làm ít hơn rất nhiều ( "a" + 1
đưa ra một ngoại lệ, nhưng 1.0 + 1
cung cấp cho bạn 2.0
*). Thật khó để đưa ý nghĩa đó vào các thuật ngữ chính thức tại sao không nên có +
một chuỗi lấy một chuỗi và một int, khi rõ ràng có các hàm khác, như lập chỉ mục, phải không?
* Trên thực tế, trong Python hiện đại, điều đó có thể được giải thích dưới dạng phân nhóm OO, vì isinstance(2, numbers.Real)
nó là đúng. Tôi không nghĩ có bất kỳ ý nghĩa nào trong đó 2
là một thể hiện của loại chuỗi trong Perl hoặc JavaScript, mặc dù trong Tcl, thực tế là như vậy, vì mọi thứ đều là một thể hiện của chuỗi.
Cuối cùng, có một định nghĩa khác, hoàn toàn trực giao, định nghĩa về kiểu gõ "mạnh" so với "yếu", trong đó "mạnh" có nghĩa là mạnh mẽ / linh hoạt / biểu cảm.
Ví dụ, Haskell cho phép bạn xác định loại đó là số, chuỗi, danh sách loại này hoặc bản đồ từ chuỗi sang loại này, đây là một cách hoàn hảo để thể hiện bất cứ điều gì có thể được giải mã từ JSON. Không có cách nào để định nghĩa một kiểu như vậy trong Java. Nhưng ít nhất Java có các kiểu tham số (chung), vì vậy bạn có thể viết một hàm lấy Danh sách T và biết rằng các phần tử thuộc loại T; các ngôn ngữ khác, như Java ban đầu, buộc bạn phải sử dụng Danh sách đối tượng và downcast. Nhưng ít nhất Java cho phép bạn tạo các kiểu mới bằng các phương thức riêng của chúng; C chỉ cho phép bạn tạo cấu trúc. Và BCPL thậm chí không có điều đó. Và như vậy để lắp ráp, trong đó các loại duy nhất là độ dài bit khác nhau.
Vì vậy, theo nghĩa đó, hệ thống loại của Haskell mạnh hơn Java hiện đại, mạnh hơn Java trước đó, mạnh hơn C, mạnh hơn BCPL.
Vậy, Python phù hợp với phổ đó ở đâu? Đó là một chút khó khăn. Trong nhiều trường hợp, gõ vịt cho phép bạn mô phỏng mọi thứ bạn có thể làm trong Haskell, và thậm chí một số điều bạn không thể làm được; chắc chắn, lỗi được bắt gặp trong thời gian chạy thay vì thời gian biên dịch, nhưng chúng vẫn bị bắt. Tuy nhiên, có những trường hợp gõ vịt không đủ. Ví dụ, trong Haskell, bạn có thể nói rằng một danh sách ints trống là danh sách các số nguyên, vì vậy bạn có thể quyết định rằng việc giảm +
trên danh sách đó sẽ trả về 0 *; trong Python, một danh sách trống là một danh sách trống; không có thông tin loại nào để giúp bạn quyết định việc giảm bớt +
nên làm gì.
* Trên thực tế, Haskell không cho phép bạn làm điều này; nếu bạn gọi hàm giảm không lấy giá trị bắt đầu trong danh sách trống, bạn sẽ gặp lỗi. Nhưng hệ thống loại của nó đủ mạnh để bạn có thể làm việc này và Python thì không.