Tìm kiếm làm rõ về những mâu thuẫn rõ ràng liên quan đến các ngôn ngữ được đánh máy yếu


178

Tôi nghĩ rằng tôi hiểu kiểu gõ mạnh , nhưng mỗi lần tôi tìm ví dụ cho kiểu gõ yếu, cuối cùng tôi lại tìm thấy các ví dụ về ngôn ngữ lập trình chỉ đơn giản là ép buộc / chuyển đổi các loại.

Chẳng hạn, trong bài viết này có tên Typing: Strong vs. Weak, Static vs. Dynamic nói rằng Python được gõ mạnh vì bạn nhận được một ngoại lệ nếu bạn cố gắng:

Con trăn

1 + "1"
Traceback (most recent call last):
File "", line 1, in ? 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Tuy nhiên, điều đó là có thể trong Java và trong C #, và chúng tôi không coi chúng được đánh máy yếu chỉ vì điều đó.

Java

  int a = 10;
  String b = "b";
  String result = a + b;
  System.out.println(result);

C #

int a = 10;
string b = "b";
string c = a + b;
Console.WriteLine(c);

Trong bài viết này có tên Ngôn ngữ loại yếu , tác giả nói rằng Perl được gõ yếu chỉ đơn giản vì tôi có thể nối một chuỗi thành một số và ngược lại mà không cần chuyển đổi rõ ràng.

Perl

$a=10;
$b="a";
$c=$a.$b;
print $c; #10a

Vì vậy, ví dụ tương tự làm cho Perl gõ yếu, nhưng không phải Java và C #?.

Trời ạ, điều này thật khó hiểu nhập mô tả hình ảnh ở đây

Các tác giả dường như ngụ ý rằng một ngôn ngữ ngăn chặn việc áp dụng các hoạt động nhất định trên các giá trị của các loại khác nhau được gõ mạnh và ngược lại có nghĩa là gõ yếu.

Do đó, tại một số thời điểm, tôi cảm thấy được nhắc nhở rằng nếu một ngôn ngữ cung cấp nhiều chuyển đổi tự động hoặc ép buộc giữa các loại (như perl) cuối cùng có thể bị coi là gõ yếu, trong khi các ngôn ngữ khác chỉ cung cấp một vài chuyển đổi có thể sẽ bị xem xét đánh máy mạnh mẽ.

Mặc dù vậy, tôi có xu hướng tin rằng tôi phải sai trong sự xen kẽ này, tôi chỉ không biết tại sao hoặc làm thế nào để giải thích nó.

Vì vậy, câu hỏi của tôi là:

  • Điều đó thực sự có ý nghĩa gì đối với một ngôn ngữ được gõ thực sự yếu?
  • Bạn có thể đề cập đến bất kỳ ví dụ hay nào về việc gõ yếu không liên quan đến chuyển đổi tự động / ép buộc tự động được thực hiện bởi ngôn ngữ không?
  • Một ngôn ngữ có thể được gõ yếu và gõ mạnh cùng một lúc?

8
Gõ mạnh so với yếu là tất cả về chuyển đổi loại (nó có thể là gì khác?) Nếu bạn muốn có một ví dụ về ngôn ngữ "rất" yếu, hãy xem điều này: killallsoftware.com/talks/wat .
Wilduck

2
@Wildduck Tất cả các ngôn ngữ cung cấp chuyển đổi loại, nhưng không phải tất cả các ngôn ngữ được coi là gõ yếu. Các ví dụ của tôi được hiển thị dưới đây cho thấy cách các lập trình viên xem xét một ngôn ngữ được gõ yếu dựa trên các ví dụ tương tự có thể có trên các ngôn ngữ khác được coi là gõ mạnh. Như vậy câu hỏi của tôi vẫn chiếm ưu thế. Sự khác biệt là gì?
Edwin Dalorzo

1
Câu trả lời ngắn, tôi nghĩ, là "Typedness" không phải là trạng thái nhị phân. Java và C # được gõ mạnh hơn nhưng không hoàn toàn.
Jodrell

3
Tôi tin rằng điều này phù hợp hơn với Kỹ thuật phần mềm .
zzzzBov

4
@Brendan Điều gì về việc tóm tắt một số float và một số nguyên? Không phải là số nguyên bị ép buộc nổi trong Python? Bây giờ bạn có nói rằng Python không được gõ mạnh mẽ không?
Edwin Dalorzo

Câu trả lời:


210

CẬP NHẬT: Câu hỏi này là chủ đề của blog của tôi vào ngày 15 tháng 10 năm 2012. Cảm ơn câu hỏi tuyệt vời!


Điều gì thực sự có nghĩa là một ngôn ngữ bị "gõ yếu"?

Nó có nghĩa là "ngôn ngữ này sử dụng một hệ thống loại mà tôi thấy khó chịu". Một ngôn ngữ "gõ mạnh" ngược lại là một ngôn ngữ có hệ thống loại mà tôi thấy dễ chịu.

Các điều khoản về cơ bản là vô nghĩa và bạn nên tránh chúng. Wikipedia liệt kê mười một ý nghĩa khác nhau cho "gõ mạnh", một số trong đó là mâu thuẫn. Điều này cho thấy tỷ lệ nhầm lẫn được tạo ra là cao trong bất kỳ cuộc trò chuyện nào liên quan đến thuật ngữ "gõ mạnh" hoặc "gõ yếu".

Tất cả những gì bạn thực sự có thể nói với bất kỳ sự chắc chắn nào là ngôn ngữ "được gõ mạnh" khi thảo luận có một số hạn chế bổ sung trong hệ thống loại, trong thời gian chạy hoặc thời gian biên dịch, rằng ngôn ngữ "gõ yếu" khi thảo luận thiếu. Những hạn chế đó có thể không thể được xác định mà không cần bối cảnh thêm.

Thay vì sử dụng "gõ mạnh" và "gõ yếu", bạn nên mô tả chi tiết loại an toàn mà bạn muốn nói. Ví dụ, C # là ngôn ngữ được nhập tĩnh và ngôn ngữ an toàn loại và ngôn ngữ an toàn bộ nhớ , đối với hầu hết các phần. C # cho phép cả ba hình thức gõ "mạnh" bị vi phạm. Toán tử cast vi phạm gõ tĩnh; nó nói với trình biên dịch "Tôi biết nhiều hơn về kiểu thời gian chạy của biểu thức này hơn là bạn làm". Nếu nhà phát triển sai, thì bộ thực thi sẽ đưa ra một ngoại lệ để bảo vệ an toàn kiểu. Nếu nhà phát triển muốn phá vỡ an toàn loại hoặc an toàn bộ nhớ, họ có thể làm như vậy bằng cách tắt hệ thống an toàn loại bằng cách tạo một khối "không an toàn". Trong một khối không an toàn, bạn có thể sử dụng ma thuật con trỏ để coi int như một float (vi phạm an toàn kiểu) hoặc để ghi vào bộ nhớ mà bạn không sở hữu. (Vi phạm an toàn bộ nhớ.)

C # áp đặt các hạn chế loại được kiểm tra ở cả thời gian biên dịch và thời gian chạy, do đó làm cho nó trở thành ngôn ngữ "được gõ mạnh" so với các ngôn ngữ kiểm tra thời gian biên dịch ít hơn hoặc kiểm tra thời gian chạy ít hơn. C # cũng cho phép bạn trong những trường hợp đặc biệt thực hiện một cách hết sức xung quanh những hạn chế đó, biến nó thành ngôn ngữ "gõ yếu" so với các ngôn ngữ không cho phép bạn thực hiện một kết thúc như vậy.

Đó là thực sự? Nó là không thể nói; nó phụ thuộc vào quan điểm của người nói và thái độ của họ đối với các tính năng ngôn ngữ khác nhau.


14
@edalorzo: Dựa trên sở thích và ý kiến ​​cá nhân về (1) khía cạnh nào của lý thuyết loại có liên quan và không liên quan, và (2) liệu một ngôn ngữ có bắt buộc phải thực thi hay chỉ khuyến khích hạn chế loại. Như tôi đã chỉ ra, người ta có thể nói một cách hợp lý rằng C # được gõ mạnh vì nó cho phép và khuyến khích gõ tĩnh, và người ta có thể nói một cách hợp lý rằng nó được gõ yếu vì nó cho phép khả năng vi phạm an toàn kiểu.
Eric Lippert

4
@edalorzo: Còn về lắp ráp, một lần nữa, đó là vấn đề quan điểm. Trình biên dịch ngôn ngữ lắp ráp sẽ không cho phép bạn di chuyển gấp đôi 64 bit từ ngăn xếp sang thanh ghi 32 bit; nó sẽ cho phép bạn di chuyển con trỏ 32 bit lên gấp đôi 64 bit từ ngăn xếp thành thanh ghi 32 bit. Theo nghĩa đó, ngôn ngữ là "typeafe" - nó áp đặt một hạn chế về tính hợp pháp của chương trình dựa trên phân loại dữ liệu. Cho dù hạn chế đó là "mạnh" hay "yếu" là vấn đề quan điểm, nhưng rõ ràng đó là hạn chế.
Eric Lippert

2
Tôi nghĩ rằng tôi thấy quan điểm của bạn bây giờ, một ngôn ngữ được đánh máy thực sự yếu sẽ phải hoàn toàn không được gõ hoặc đơn điệu, điều mà trong cuộc sống thực tế là không thể. Như vậy, bất kỳ ngôn ngữ nào cũng có định nghĩa nhất định về loại, an toàn và tùy thuộc vào số lượng lỗ mà ngôn ngữ đó cung cấp để vi phạm hoặc thao túng dữ liệu hoặc loại dữ liệu của bạn, bạn có thể sẽ xem xét nó ít nhiều được gõ, thậm chí có thể trong bối cảnh nhất định chỉ.
Edwin Dalorzo

7
@edalorzo: Đúng. Ví dụ, phép tính lambda chưa được gõ là về mức độ đánh máy yếu như bạn có thể nhận được. Mỗi chức năng là một chức năng từ một chức năng đến một chức năng; mọi dữ liệu có thể được chuyển đến bất kỳ chức năng nào mà không bị hạn chế vì mọi thứ đều thuộc "cùng loại". Tính hợp lệ của một biểu thức trong phép tính lambda chưa được đánh dấu chỉ phụ thuộc vào hình thức cú pháp của nó, chứ không phụ thuộc vào phân tích ngữ nghĩa phân loại các biểu thức nhất định là có một số loại nhất định.
Eric Lippert

3
@Mark Tôi sẽ cho anh ta thêm +1 để dự đoán rằng mọi người sẽ cung cấp các cách hiểu khác nhau về chủ đề này. "Gõ yếu" này dường như là một "khái niệm thần thoại" hay "huyền thoại đô thị", mọi người đều đã thấy nó, nhưng không ai có thể chứng minh nó tồn tại :-)
Edwin Dalorzo

64

Như những người khác đã lưu ý, các thuật ngữ "gõ mạnh" và "gõ yếu" có rất nhiều ý nghĩa khác nhau mà không có câu trả lời duy nhất cho câu hỏi của bạn. Tuy nhiên, vì bạn đặc biệt đề cập đến Perl trong câu hỏi của bạn, hãy để tôi cố gắng giải thích theo nghĩa nào Perl được gõ yếu.

Vấn đề là, trong Perl, không có thứ gọi là "biến số nguyên", "biến float", "biến chuỗi" hay "biến boolean". Trong thực tế, theo như người dùng có thể (thường) nói, thậm chí không có các giá trị nguyên, float, chuỗi hoặc boolean : tất cả những gì bạn có là "vô hướng", đó là tất cả những điều này cùng một lúc. Vì vậy, bạn có thể, ví dụ, viết:

$foo = "123" + "456";           # $foo = 579
$bar = substr($foo, 2, 1);      # $bar = 9
$bar .= " lives";               # $bar = "9 lives"
$foo -= $bar;                   # $foo = 579 - 9 = 570

Tất nhiên, như bạn lưu ý chính xác, tất cả những điều này có thể được xem như là kiểu ép buộc. Nhưng vấn đề là, trong Perl, các loại luôn bị ép buộc. Trên thực tế, người dùng khá khó để biết "loại" bên trong của biến có thể là gì: tại dòng 2 trong ví dụ của tôi ở trên, hỏi xem giá trị của $barchuỗi "9"là số hay số 9là vô nghĩa, vì, như, như Perl có liên quan, đó là những điều tương tự . Thật vậy, thậm chí có thể một vô hướng Perl có thể có cả chuỗi và giá trị số cùng một lúc, ví dụ như trường hợp $foosau dòng 2 ở trên.

Mặt trái của tất cả những điều này là, vì các biến Perl được tháo gỡ (hoặc, đúng hơn, không để lộ kiểu bên trong của chúng cho người dùng), các toán tử không thể bị quá tải để làm những việc khác nhau cho các loại đối số khác nhau; bạn không thể chỉ nói "toán tử này sẽ thực hiện X cho các số và Y cho các chuỗi", bởi vì toán tử không thể (không) cho biết loại đối số của nó là giá trị nào.

Do đó, ví dụ, Perl có và cần cả toán tử cộng số ( +) và toán tử nối chuỗi ( .): như bạn đã thấy ở trên, việc thêm chuỗi ( "1" + "2" == "3") hoặc nối các số ( 1 . 2 == 12) là hoàn toàn tốt . Tương tự như vậy, các nhà khai thác so sánh số ==, !=, <, >, <=, >=<=>so sánh các giá trị số của các đối số của họ, trong khi các nhà khai thác chuỗi so sánh eq, ne, lt, gt, le, gecmpso sánh chúng tự từ điển như dây đàn. Vì vậy 2 < 10, nhưng 2 gt 10(nhưng "02" lt 10, trong khi "02" == 2). (Nhắc bạn, một số ngôn ngữ khác , như JavaScript, cố gắng điều chỉnh kiểu gõ yếu như Perl trong khicũng làm quá tải toán tử. Điều này thường dẫn đến sự xấu xí, như mất tính kết hợp cho +.)

(Điểm nổi bật của thuốc mỡ ở đây là, vì lý do lịch sử, Perl 5 có một vài trường hợp góc, giống như các toán tử logic bitwise, hành vi của chúng phụ thuộc vào biểu diễn bên trong của các đối số của chúng. đại diện nội bộ có thể thay đổi vì những lý do đáng ngạc nhiên, và do đó, việc dự đoán những gì các nhà khai thác đó làm trong một tình huống nhất định có thể khó khăn.)

Tất cả những gì đã nói, người ta có thể lập luận rằng Perl không có loại mạnh; chúng không phải là loại bạn có thể mong đợi. Cụ thể, ngoài loại "vô hướng" đã thảo luận ở trên, Perl còn có hai loại có cấu trúc: "mảng" và "hàm băm". Chúng rất khác biệt so với vô hướng, đến điểm mà các biến Perl có các sigils khác nhau biểu thị loại của chúng ( $đối với vô hướng, @đối với mảng, %đối với hàm băm) 1 . Có những quy tắc ép buộc giữa các loại, vì vậy bạn có thể viết ví dụ %foo = @bar, nhưng nhiều người trong số họ là khá lossy: ví dụ, $foo = @bargiao cho chiều dài của mảng @barđến$foo, không phải nội dung của nó. (Ngoài ra, có một vài loại lạ khác, như kiểu chữ và tay cầm I / O, mà bạn không thường thấy bị lộ.)

Ngoài ra, một nhược điểm nhỏ trong thiết kế đẹp này là sự tồn tại của các loại tham chiếu, là một loại vô hướng đặc biệt (và có thể được phân biệt với các vô hướng thông thường, sử dụng reftoán tử). Có thể sử dụng các tham chiếu như các vô hướng bình thường, nhưng các giá trị chuỗi / số của chúng không đặc biệt hữu ích và chúng có xu hướng mất tính tham chiếu đặc biệt nếu bạn sửa đổi chúng bằng các phép toán vô hướng bình thường. Ngoài ra, bất kỳ biến Perl 2 nào cũng có thể được chỉnh blesssửa thành một lớp, biến nó thành một đối tượng của lớp đó; hệ thống lớp OO trong Perl có phần trực giao với hệ thống kiểu nguyên thủy (hay không chữ) được mô tả ở trên, mặc dù nó cũng "yếu" theo nghĩa là gõ vịtmô hình. Ý kiến ​​chung là, nếu bạn thấy mình đang kiểm tra lớp của một đối tượng trong Perl, bạn đang làm gì đó sai.


1 Trên thực tế, sigil biểu thị loại giá trị được truy cập, do đó, ví dụ vô hướng đầu tiên trong mảng @foođược ký hiệu $foo[0]. Xem perlfaq4 để biết thêm chi tiết.

2 Đối tượng trong Perl (thường) được truy cập thông qua các tham chiếu đến chúng, nhưng cái thực sự có blessed là biến (có thể ẩn danh) mà các điểm tham chiếu đến. Tuy nhiên, phước lành thực sự là một thuộc tính của biến, không phải là giá trị của nó, vì vậy, việc gán biến may mắn thực sự cho một biến khác chỉ mang lại cho bạn một bản sao nông cạn, không được ban phước của nó. Xem perlobj để biết thêm chi tiết.


19

Ngoài những gì Eric đã nói, hãy xem xét mã C sau:

void f(void* x);

f(42);
f("hello");

Trái ngược với các ngôn ngữ như Python, C #, Java hoặc whatnot, phần trên được gõ yếu vì chúng tôi mất thông tin loại. Eric đã chỉ ra một cách chính xác rằng trong C #, chúng ta có thể phá vỡ trình biên dịch bằng cách truyền, nói một cách hiệu quả với nó, tôi biết nhiều hơn về loại biến này so với bạn.

Nhưng ngay cả khi đó, thời gian chạy vẫn sẽ kiểm tra loại! Nếu diễn viên không hợp lệ, hệ thống thời gian chạy sẽ bắt nó và ném ngoại lệ.

Với kiểu xóa, điều này không xảy ra - thông tin loại sẽ bị loại bỏ. Một diễn viên void*trong C thực hiện chính xác điều đó. Về vấn đề này, ở trên về cơ bản là khác với khai báo phương pháp C # như void f(Object x).

(Về mặt kỹ thuật, C # cũng cho phép xóa kiểu thông qua mã không an toàn hoặc sắp xếp theo thứ tự.)

Đây là đánh máy yếu như nó được. Mọi thứ khác chỉ là vấn đề kiểm tra kiểu tĩnh so với kiểu động, tức là thời điểm khi một loại được kiểm tra.


1
+1 Điểm hay, bây giờ bạn đã khiến tôi nghĩ về việc xóa kiểu như là một tính năng cũng có thể ám chỉ "sự đánh máy yếu". Cũng có kiểu xóa trong Java và trong thời gian chạy, hệ thống kiểu sẽ cho phép bạn vi phạm các ràng buộc mà trình biên dịch sẽ không bao giờ phê duyệt. Ví dụ C là tuyệt vời để minh họa điểm.
Edwin Dalorzo

1
Đồng ý, có những lớp để hành tây, hoặc địa ngục. Đây có vẻ là một định nghĩa quan trọng hơn của loại yếu.
Jodrell

1
@edalorzo Tôi không nghĩ điều này hoàn toàn giống nhau vì mặc dù Java cho phép bạn phá vỡ trình biên dịch, hệ thống kiểu thời gian chạy vẫn sẽ bắt lỗi. Vì vậy, hệ thống kiểu thời gian chạy Java được gõ mạnh về vấn đề này (có các trường hợp ngoại lệ, ví dụ: nơi có thể sử dụng sự phản chiếu để phá vỡ kiểm soát truy cập).
Konrad Rudolph

1
@edalorzo Bạn chỉ có thể phá vỡ trình biên dịch theo cách này, không phải hệ thống thời gian chạy. Điều quan trọng là phải nhận ra rằng các ngôn ngữ như Java và C # (và ở một mức độ nhất định cũng là C ++) có một hệ thống loại được đảm bảo hai lần: một lần vào thời gian biên dịch và một lần khi chạy. void*vượt qua cả hai loại kiểm tra. Loại xóa chung không có, nó chỉ phá vỡ các kiểm tra thời gian biên dịch. Nó chính xác như diễn viên rõ ràng (được đề cập bởi Eric) về vấn đề này.
Konrad Rudolph

1
@edalorzo Re sự nhầm lẫn của bạn: chúng ta không nên. Sự phân biệt là trôi chảy. Và đúng vậy, kiểu xóa làm cho Java yếu đi trong vấn đề này. Quan điểm của tôi là ngay cả khi xóa loại chung, bạn vẫn không thể phá vỡ các kiểm tra loại thời gian chạy trừ khi bạn cũng sử dụng phản xạ .
Konrad Rudolph

14

Một ví dụ hoàn hảo đến từ bài viết trên wikipedia của Strong Typing :

Nói chung, gõ mạnh ngụ ý rằng ngôn ngữ lập trình đặt ra các hạn chế nghiêm trọng đối với việc xen kẽ được phép xảy ra.

Gõ yếu

a = 2
b = "2"

concatenate(a, b) # returns "22"
add(a, b) # returns 4

Đánh máy mạnh

a = 2
b = "2"

concatenate(a, b) # Type Error
add(a, b) # Type Error
concatenate(str(a), b) #Returns "22"
add(a, int(b)) # Returns 4

Lưu ý rằng một ngôn ngữ gõ yếu có thể xen kẽ các loại khác nhau mà không có lỗi. Một ngôn ngữ loại mạnh đòi hỏi các loại đầu vào phải là loại dự kiến. Trong một ngôn ngữ loại mạnh, một loại có thể được chuyển đổi ( str(a)chuyển đổi một số nguyên thành một chuỗi) hoặc cast ( int(b)).

Tất cả điều này phụ thuộc vào việc giải thích gõ.


3
Nhưng điều này dẫn đến các ví dụ mâu thuẫn được cung cấp trong câu hỏi. Một ngôn ngữ được gõ mạnh có thể bao gồm sự ép buộc ngầm có nghĩa là (hoặc cả hai) trong hai ví dụ "Lỗi loại" của bạn được tự động chuyển thành có liên quan đến hai ví dụ thứ hai, nhưng nói chung, ngôn ngữ đó vẫn được gõ mạnh.
Đánh dấu

3
Thật. Tôi đoán bạn có thể nói có nhiều mức độ khác nhau của gõ mạnh và gõ yếu. Chuyển đổi ngầm định có thể có nghĩa là ngôn ngữ ít được gõ mạnh hơn ngôn ngữ không thực hiện chuyển đổi ngầm.
SaulBack

4

Tôi muốn đóng góp cho cuộc thảo luận với nghiên cứu của riêng tôi về chủ đề này, khi những người khác bình luận và đóng góp Tôi đã đọc câu trả lời của họ và theo dõi tài liệu tham khảo của họ và tôi đã tìm thấy thông tin thú vị. Như đã đề xuất, có thể phần lớn điều này sẽ được thảo luận tốt hơn trong diễn đàn Lập trình viên, vì nó dường như mang tính lý thuyết nhiều hơn là thực tế.

Từ quan điểm lý thuyết, tôi nghĩ rằng bài viết của Luca Cardelli và Peter Wegner có tên Về cách hiểu, trừu tượng hóa dữ liệu và đa hình có một trong những lập luận tốt nhất mà tôi đã đọc.

Một loại có thể được xem như là một bộ quần áo (hoặc một bộ giáp sắt) bảo vệ một cơ untyped đại diện từ việc sử dụng tùy ý hoặc ngoài ý muốn. Nó cung cấp một lớp bảo vệ che giấu biểu diễn bên dưới và hạn chế cách các đối tượng có thể tương tác với các đối tượng khác. Trong một hệ thống không định kiểu đối tượng không định kiểu là khỏa thân trong đó các đại diện cơ bản được tiếp xúc cho mọi người thấy. Vi phạm hệ thống loại liên quan đến việc loại bỏ bộ quần áo bảo hộ và hoạt động trực tiếp trên đại diện trần trụi.

Tuyên bố này dường như gợi ý rằng việc gõ yếu sẽ cho phép chúng ta truy cập cấu trúc bên trong của một loại và thao tác với nó như thể nó là một thứ khác (loại khác). Có lẽ những gì chúng ta có thể làm với mã không an toàn (được đề cập bởi Eric) hoặc với các con trỏ xóa loại c được đề cập bởi Konrad.

Bài báo tiếp tục ...

Các ngôn ngữ trong đó tất cả các biểu thức là nhất quán loại được gọi là ngôn ngữ được gõ mạnh. Nếu một ngôn ngữ được gõ mạnh, trình biên dịch của nó có thể đảm bảo rằng các chương trình mà nó chấp nhận sẽ thực thi mà không có lỗi loại. Nói chung, chúng ta nên cố gắng gõ mạnh và chấp nhận gõ tĩnh bất cứ khi nào có thể. Lưu ý rằng mọi ngôn ngữ gõ tĩnh được gõ mạnh nhưng ngược lại không nhất thiết phải đúng.

Như vậy, gõ mạnh có nghĩa là không có lỗi loại, tôi chỉ có thể giả sử rằng gõ yếu có nghĩa ngược lại: sự hiện diện của lỗi loại. Tại thời gian chạy hoặc biên dịch thời gian? Có vẻ không liên quan ở đây.

Điều buồn cười, theo định nghĩa này, một ngôn ngữ với các kiểu ép buộc mạnh mẽ như Perl sẽ được coi là được gõ mạnh, bởi vì hệ thống không bị lỗi, nhưng nó đang xử lý các loại bằng cách ép chúng thành các tương đương phù hợp và được xác định rõ.

Mặt khác, tôi có thể nói hơn trợ cấp của ClassCastExceptionArrayStoreException(trong Java) và InvalidCastException, ArrayTypeMismatchException(trong C #) sẽ chỉ ra mức độ gõ yếu, ít nhất là tại thời gian biên dịch? Câu trả lời của Eric dường như đồng ý với điều này.

Trong một bài viết thứ hai có tên Lập trình kiểu chữ được cung cấp trong một trong những tài liệu tham khảo được cung cấp trong một trong những câu trả lời trong câu hỏi này, Luca Cardelli đi sâu vào khái niệm vi phạm loại:

Hầu hết các ngôn ngữ lập trình hệ thống cho phép vi phạm loại tùy ý, một số bừa bãi, một số chỉ trong các phần bị hạn chế của chương trình. Các hoạt động liên quan đến vi phạm loại được gọi là không có căn cứ. Loại vi phạm thuộc một số lớp [trong đó chúng tôi có thể đề cập]:

Các ép buộc giá trị cơ bản : Chúng bao gồm các chuyển đổi giữa các số nguyên, booleans, ký tự, bộ, v.v. Không cần vi phạm kiểu ở đây, vì các giao diện tích hợp có thể được cung cấp để thực hiện các ép buộc theo cách âm thanh.

Như vậy, các kiểu ép buộc như được cung cấp bởi các nhà khai thác có thể được coi là vi phạm kiểu, nhưng trừ khi chúng phá vỡ tính nhất quán của hệ thống loại, chúng tôi có thể nói rằng chúng không dẫn đến một hệ thống được gõ yếu.

Dựa trên điều này, cả Python, Perl, Java hay C # đều không được gõ yếu.

Cardelli đề cập đến hai loại lỗi mà tôi rất xem xét các trường hợp gõ thực sự yếu:

Địa chỉ số học. Nếu cần, cần có một giao diện tích hợp (không có căn cứ), cung cấp các hoạt động đầy đủ về địa chỉ và chuyển đổi loại. Các tình huống khác nhau liên quan đến con trỏ vào heap (rất nguy hiểm với việc di chuyển bộ thu), con trỏ tới ngăn xếp, con trỏ đến vùng tĩnh và con trỏ vào không gian địa chỉ khác. Đôi khi lập chỉ mục mảng có thể thay thế số học địa chỉ. Ánh xạ bộ nhớ. Điều này liên quan đến việc xem xét một vùng bộ nhớ như một mảng không có cấu trúc, mặc dù nó chứa dữ liệu có cấu trúc. Đây là điển hình của phân bổ bộ nhớ và người thu gom.

Loại điều này có thể có trong các ngôn ngữ như C (được đề cập bởi Konrad) hoặc thông qua mã không an toàn trong .Net (được đề cập bởi Eric) sẽ thực sự ngụ ý việc gõ yếu.

Tôi tin rằng câu trả lời tốt nhất cho đến nay là của Eric, bởi vì định nghĩa của các khái niệm này rất lý thuyết và khi nói đến một ngôn ngữ cụ thể, việc giải thích tất cả các khái niệm này có thể dẫn đến các kết luận gây tranh cãi khác nhau.


4

Gõ yếu thực sự có nghĩa là một tỷ lệ cao các loại có thể bị ép buộc ngầm, cố gắng đoán những gì các lập trình viên dự định.

Gõ mạnh có nghĩa là các loại không bị ép buộc, hoặc ít nhất là bị ép buộc ít hơn.

Gõ tĩnh có nghĩa là các loại biến của bạn được xác định tại thời điểm biên dịch.

Nhiều người gần đây đã nhầm lẫn "rõ ràng là gõ" với "gõ mạnh". "Nhập một cách rõ ràng" có nghĩa là bạn khai báo các loại biến của mình một cách rõ ràng.

Python chủ yếu được gõ mạnh, mặc dù bạn có thể sử dụng hầu hết mọi thứ trong ngữ cảnh boolean và booleans có thể được sử dụng trong ngữ cảnh số nguyên và bạn có thể sử dụng một số nguyên trong ngữ cảnh nổi. Nó không được gõ rõ ràng, vì bạn không cần phải khai báo các loại của mình (ngoại trừ Cython, không hoàn toàn là python, mặc dù thú vị). Nó cũng không được gõ tĩnh.

C và C ++ rõ ràng được gõ, gõ tĩnh và được gõ mạnh, bởi vì bạn khai báo các kiểu, loại được xác định tại thời điểm biên dịch và bạn có thể trộn các số nguyên và con trỏ, hoặc số nguyên và nhân đôi, hoặc thậm chí bỏ một con trỏ thành một loại vào một con trỏ đến loại khác.

Haskell là một ví dụ thú vị, bởi vì nó không được gõ rõ ràng, nhưng nó cũng được gõ một cách tĩnh và mạnh mẽ.


+1 Bởi vì tôi thích thuật ngữ được đặt ra "gõ rõ ràng", phân loại các ngôn ngữ như Java và C # trong đó bạn phải khai báo rõ ràng các loại và phân biệt chúng với các ngôn ngữ kiểu tĩnh khác như Haskell và Scala trong đó suy luận kiểu đóng vai trò quan trọng và điều này thường Như bạn nói, làm mọi người bối rối và khiến họ tin rằng những ngôn ngữ này được gõ động.
Edwin Dalorzo

3

Kiểu gõ mạnh <=> không chỉ liên quan đến tính liên tục về mức độ hoặc mức độ ít của các giá trị được ép buộc tự động bởi ngôn ngữ cho kiểu dữ liệu này sang kiểu dữ liệu khác, mà là các giá trị thực tế được gõ mạnh hay yếu . Trong Python và Java và chủ yếu là trong C #, các giá trị có kiểu được đặt trong đá. Trong Perl, không quá nhiều - thực sự chỉ có một số ít các giá trị khác nhau để lưu trữ trong một biến.

Chúng ta hãy mở từng trường hợp một.


Con trăn

Trong ví dụ Python 1 + "1", +toán tử gọi __add__kiểu for intcho chuỗi đó "1"là một đối số - tuy nhiên, điều này dẫn đến NotImcellenceed:

>>> (1).__add__('1')
NotImplemented

Tiếp theo, trình thông dịch thử các __radd__str:

>>> '1'.__radd__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__radd__'

Khi nó thất bại, các +nhà điều hành thất bại với kết quả TypeError: unsupported operand type(s) for +: 'int' and 'str'. Như vậy, ngoại lệ không nói nhiều về việc gõ mạnh, nhưng thực tế là toán tử + không tự động ép các đối số của nó thành cùng loại, là một con trỏ cho thực tế rằng Python không phải là ngôn ngữ được gõ yếu nhất trong tính liên tục.

Mặt khác, trong Python 'a' * 5 được triển khai:

>>> 'a' * 5
'aaaaa'

Đó là,

>>> 'a'.__mul__(5)
'aaaaa'

Thực tế là hoạt động khác nhau đòi hỏi một số thao tác gõ mạnh - tuy nhiên ngược lại với việc *ép các giá trị thành số trước khi nhân vẫn không nhất thiết làm cho các giá trị được gõ yếu.


Java

Ví dụ Java, String result = "1" + 1;chỉ hoạt động vì thực tế là thuận tiện, toán tử +bị quá tải cho chuỗi. +Toán tử Java thay thế chuỗi bằng cách tạo một StringBuilder(xem phần này ):

String result = a + b;
// becomes something like
String result = new StringBuilder().append(a).append(b).toString()

Đây đúng hơn là một ví dụ về việc gõ rất tĩnh, không có sự ép buộc thực tế - StringBuildercó một phương pháp append(Object)được sử dụng cụ thể ở đây. Các tài liệu nói như sau:

Nối biểu diễn chuỗi của Objectđối số.

Hiệu ứng tổng thể chính xác như thể đối số được chuyển đổi thành một chuỗi theo phương thức String.valueOf(Object)và các ký tự của chuỗi đó sau đó được thêm vào chuỗi ký tự này.

String.valueOfSau đó ở đâu

Trả về biểu diễn chuỗi của đối số Object. [Trả về] nếu đối số là null, thì một chuỗi bằng "null"; mặt khác, giá trị của obj.toString()được trả lại.

Do đó, đây là một trường hợp hoàn toàn không ép buộc bởi ngôn ngữ - ủy thác mọi mối quan tâm cho chính các đối tượng.


C #

Theo câu trả lời của Jon Skeet ở đây , toán tử +thậm chí không bị quá tải cho stringlớp - gần giống với Java, đây chỉ là sự tiện lợi được tạo bởi trình biên dịch, nhờ cả gõ tĩnh và gõ mạnh.


Perl

Như perldata giải thích,

Perl có ba loại dữ liệu tích hợp: vô hướng, mảng vô hướng và mảng kết hợp của vô hướng, được gọi là "băm". Một vô hướng là một chuỗi đơn (có kích thước bất kỳ, chỉ giới hạn bởi bộ nhớ khả dụng), số hoặc tham chiếu đến một cái gì đó (sẽ được thảo luận trong perlref). Các mảng thông thường được sắp xếp danh sách các vô hướng được lập chỉ mục theo số, bắt đầu bằng 0. Băm là các tập hợp không có thứ tự của các giá trị vô hướng được lập chỉ mục bởi khóa chuỗi liên kết của chúng.

Tuy nhiên, Perl không có một kiểu dữ liệu riêng cho các số, booleans, chuỗi, null, undefineds, tham chiếu đến các đối tượng khác, v.v. - nó chỉ có một loại cho tất cả, loại vô hướng; 0 là một giá trị vô hướng nhiều như "0". Một biến vô hướng được đặt thành một chuỗi thực sự có thể thay đổi thành một số và từ đó hành xử khác với "chỉ là một chuỗi", nếu nó được truy cập trong ngữ cảnh số. Vô hướng có thể chứa bất cứ thứ gì trong Perl, nó là đối tượng nhiều như nó tồn tại trong hệ thống. trong khi trong Python, các tên chỉ để chỉ các đối tượng, trong Perl, các giá trị vô hướng trong các tên là các đối tượng có thể thay đổi. Hơn nữa, hệ thống Kiểu hướng đối tượng được dán ở trên này: chỉ có 3 kiểu dữ liệu trong perl - vô hướng, danh sách và băm. Một đối tượng do người dùng định nghĩa trong Perl là một tham chiếu (là con trỏ tới bất kỳ 3 mục nào trước đó) blessed cho một gói - bạn có thể nhận bất kỳ giá trị nào như vậy và ban phước cho bất kỳ lớp nào bất cứ lúc nào bạn muốn.

Perl thậm chí cho phép bạn thay đổi các lớp giá trị theo ý muốn - điều này không thể thực hiện được trong Python để tạo giá trị của một số lớp mà bạn cần xây dựng rõ ràng giá trị thuộc về lớp đó bằng object.__new__hoặc tương tự. Trong Python bạn không thể thực sự thay đổi bản chất của đối tượng sau khi tạo, trong Perl bạn có thể làm bất cứ điều gì:

package Foo;
package Bar;

my $val = 42;
# $val is now a scalar value set from double
bless \$val, Foo;
# all references to $val now belong to class Foo
my $obj = \$val;
# now $obj refers to the SV stored in $val
# thus this prints: Foo=SCALAR(0x1c7d8c8)
print \$val, "\n"; 
# all references to $val now belong to class Bar
bless \$val, Bar;
# thus this prints Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# we change the value stored in $val from number to a string
$val = 'abc';
# yet still the SV is blessed: Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# and on the course, the $obj now refers to a "Bar" even though
# at the time of copying it did refer to a "Foo".
print $obj, "\n";

do đó, danh tính loại bị ràng buộc yếu với biến và nó có thể được thay đổi thông qua bất kỳ tham chiếu nào khi đang bay. Trong thực tế, nếu bạn làm

my $another = $val;

\$anotherkhông có bản sắc giai cấp, mặc dù \$valvẫn sẽ cung cấp tài liệu tham khảo may mắn.


TL; DR

Có nhiều thứ hơn về việc gõ yếu vào Perl thay vì chỉ ép buộc tự động, và điều đó nói thêm rằng các loại giá trị không được đặt thành đá, không giống như Python là ngôn ngữ được gõ rất mạnh. Python cung cấp cho TypeErrortrên 1 + "1"là một dấu hiệu cho thấy ngôn ngữ được gõ mạnh mẽ, mặc dù một trái làm một cái gì đó có ích, như trong Java hoặc C # không loại trừ họ là ngôn ngữ mạnh mẽ gõ.


Điều này là hoàn toàn bối rối. Rằng 5 biến Perl không có loại không có bất kỳ giá trị nào , luôn có loại.
Jim Balter

@JimBalter tốt, vâng, một giá trị có một loại trong đó là một chuỗi hoặc một số và nó có thể hoạt động khác nhau trong một số ngữ cảnh tùy thuộc vào việc biến vô hướng có chứa một chuỗi hoặc một số; nhưng giá trị chứa trong một biến có thể thay đổi loại chỉ bằng cách truy cập vào biến và vì chính giá trị đó nằm trong biến, nên các giá trị có thể được coi là có thể thay đổi giữa các loại.
Antti Haapala

Các giá trị không thay đổi loại - điều đó không mạch lạc; một giá trị luôn luôn là một loại . Giá trị mà một biến chứa có thể thay đổi. Thay đổi từ 1 thành "1" cũng giống như thay đổi về giá trị như thay đổi từ 1 thành 2.
Jim Balter

Một ngôn ngữ được gõ yếu như Perl cho phép loại thay đổi giá trị trước đây xảy ra hoàn toàn tùy thuộc vào ngữ cảnh. Nhưng ngay cả C ++ cũng cho phép chuyển đổi ngầm định như vậy thông qua các định nghĩa toán tử. Gõ yếu là một tài sản rất không chính thức và thực sự không phải là một cách hữu ích để mô tả các ngôn ngữ, như Eric Lippert đã chỉ ra.
Jim Balter

PS Có thể chỉ ra rằng, ngay cả trong Perl, <chữ số> và "<chữ số>" có các giá trị khác nhau, không chỉ các loại khác nhau. Perl làm cho <chữ số> và "<chữ số>" dường như có cùng giá trị trong hầu hết các trường hợp thông qua chuyển đổi ngầm định , nhưng ảo ảnh không hoàn thành; ví dụ: "12" | "34" là 36 trong khi 12 | 34 là 46. Một ví dụ khác là "00" bằng số với 00 trong hầu hết các bối cảnh, nhưng không phải trong bối cảnh boolean, trong đó "00" là đúng nhưng 00 là sai.
Jim Balter

1

Như nhiều người khác đã bày tỏ, toàn bộ khái niệm gõ "mạnh" và "yếu" là có vấn đề.

Là một nguyên mẫu, Smalltalk được gõ rất mạnh - nó sẽ luôn đưa ra một ngoại lệ nếu một hoạt động giữa hai đối tượng không tương thích. Tuy nhiên, tôi nghi ngờ một vài người trong danh sách này sẽ gọi Smalltalk là ngôn ngữ được gõ mạnh, bởi vì nó được gõ động.

Tôi thấy khái niệm gõ "tĩnh" so với "động" hữu ích hơn so với "mạnh" so với "yếu". Một ngôn ngữ gõ tĩnh có tất cả các loại được tìm ra tại thời gian biên dịch và lập trình viên phải khai báo rõ ràng nếu không.

Tương phản với một ngôn ngữ gõ động, trong đó việc gõ được thực hiện trong thời gian chạy. Đây thường là một yêu cầu cho các ngôn ngữ đa hình, do đó quyết định về việc liệu một hoạt động giữa hai đối tượng có hợp pháp không phải được lập trình viên quyết định trước.

Trong các ngôn ngữ đa hình, được gõ động (như Smalltalk và Ruby), sẽ hữu ích hơn khi nghĩ về "loại" là "sự phù hợp với giao thức". Nếu một đối tượng tuân theo một giao thức giống như cách một đối tượng khác thực hiện - ngay cả khi hai đối tượng không chia sẻ bất kỳ sự kế thừa hoặc mixin hoặc voodoo nào khác - chúng được hệ thống thời gian chạy coi là "cùng loại". Chính xác hơn, một đối tượng trong các hệ thống như vậy là tự trị và có thể quyết định xem nó có hợp lý để trả lời bất kỳ thông điệp cụ thể nào đề cập đến bất kỳ đối số cụ thể nào không.

Muốn có một đối tượng có thể thực hiện một số phản hồi có ý nghĩa đối với thông báo "+" với đối số mô tả màu xanh lam? Bạn có thể làm điều đó trong các ngôn ngữ gõ động, nhưng đó là một nỗi đau trong các ngôn ngữ gõ tĩnh.


3
Tôi nghĩ rằng khái niệm động và gõ tĩnh không được thảo luận. Mặc dù tôi phải nói rằng tôi không tin rằng đa hình dù sao cũng bị vô hiệu hóa trong các ngôn ngữ kiểu tĩnh. Cuối cùng, hệ thống loại xác minh nếu một hoạt động nhất định có thể áp dụng cho các toán hạng đã cho, cho dù là trong thời gian chạy hay tại thời gian biên dịch. Ngoài ra, các dạng đa hình khác, như các hàm và các lớp tham số cho phép kết hợp các loại trong các ngôn ngữ kiểu tĩnh theo cách mà bạn mô tả là rất khó khi so sánh với kiểu gõ động, thậm chí còn đẹp hơn nếu suy luận kiểu được cung cấp.
Edwin Dalorzo

0

Tôi thích câu trả lời của @Eric Lippert , nhưng để giải quyết câu hỏi - các ngôn ngữ được gõ mạnh thường có kiến ​​thức rõ ràng về các loại biến tại mỗi điểm của chương trình. Các ngôn ngữ được gõ yếu thì không, vì vậy chúng có thể cố gắng thực hiện một thao tác có thể không thực hiện được đối với một loại cụ thể. Nó nghĩ rằng cách dễ nhất để thấy điều này là trong một chức năng. C ++:

void func(string a) {...}

Biến ađược biết là thuộc kiểu chuỗi và mọi hoạt động không tương thích sẽ được bắt gặp tại thời điểm biên dịch.

Con trăn

def func(a)
  ...

Biến acó thể là bất cứ thứ gì và chúng ta có thể có mã gọi một phương thức không hợp lệ, sẽ chỉ bị bắt khi chạy.


12
Tôi nghĩ rằng bạn có thể nhầm lẫn giữa gõ động so với gõ tĩnh với gõ mạnh so với gõ yếu. Trong cả hai phiên bản mã của bạn, các hệ thống loại thời gian chạy đều biết rất rõ rằng a là một chuỗi. Nó chỉ là trong trường hợp đầu tiên, trình biên dịch có thể cho bạn biết rằng, trong lần thứ hai thì không thể. Nhưng điều này không làm cho bất kỳ ngôn ngữ nào trong số này bị đánh máy yếu.
Edwin Dalorzo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.