Chức năng nào mà gõ động cho phép? [đóng cửa]


91

Tôi đã sử dụng python được vài ngày và tôi nghĩ rằng tôi hiểu sự khác biệt giữa kiểu gõ động và tĩnh. Những gì tôi không hiểu là trong hoàn cảnh nào nó sẽ được ưa thích. Nó linh hoạt và dễ đọc, nhưng với chi phí kiểm tra thời gian chạy nhiều hơn và kiểm tra đơn vị yêu cầu bổ sung.

Ngoài các tiêu chí phi chức năng như tính linh hoạt và dễ đọc, còn lý do nào để chọn kiểu gõ động? Tôi có thể làm gì với kiểu gõ động mà không thể khác được? Ví dụ mã cụ thể nào bạn có thể nghĩ về điều đó minh họa một lợi thế cụ thể của kiểu gõ động?


5
Về mặt lý thuyết, không có gì bạn không thể làm được, miễn là các ngôn ngữ là Turing Complete . Câu hỏi thú vị hơn với tôi là cái gì dễ hay tự nhiên trong cái này so với cái kia. Có những điều tôi thường xuyên làm trong Python mà tôi thậm chí sẽ không xem xét trong C ++ mặc dù tôi biết nó có khả năng.
Đánh dấu tiền chuộc

28
Như Chris Smith viết trong bài tiểu luận xuất sắc Những điều cần biết trước khi tranh luận về các hệ thống loại : "Vấn đề, trong trường hợp này là hầu hết các lập trình viên có kinh nghiệm hạn chế và đã không thử nhiều ngôn ngữ. Đối với bối cảnh, ở đây, sáu hoặc bảy không được tính là "rất nhiều." ... Hai hậu quả thú vị của việc này là: (1) Nhiều lập trình viên đã sử dụng ngôn ngữ gõ rất kém. (2) Nhiều lập trình viên đã sử dụng ngôn ngữ gõ động rất kém. "
Daniel Pryden

3
@suslik: Nếu ngôn ngữ nguyên thủy có các loại vô nghĩa, thì tất nhiên bạn có thể làm những điều vô nghĩa với các loại. Điều đó không có gì để làm với sự khác biệt giữa gõ tĩnh và động.
Jon Purdy

10
@CzarekTomczak: Đó là một tính năng của một số ngôn ngữ được gõ động, vâng. Nhưng có thể một ngôn ngữ gõ tĩnh có thể sửa đổi trong thời gian chạy. Ví dụ: Visual Studio cho phép bạn viết lại mã C # trong khi bạn đang ở điểm dừng trong trình gỡ lỗi và thậm chí tua lại con trỏ lệnh để chạy lại mã của bạn với các thay đổi mới. Như tôi đã trích dẫn Chris Smith trong bình luận khác của tôi: "Nhiều lập trình viên đã sử dụng các ngôn ngữ gõ tĩnh rất kém" - đừng phán xét tất cả các ngôn ngữ được gõ tĩnh bởi những ngôn ngữ bạn biết.
Daniel Pryden

11
@WarrenP: Bạn khẳng định rằng "các hệ thống loại động làm giảm số lượng hành trình bổ sung mà tôi phải nhập" - nhưng sau đó bạn so sánh Python với C ++. Đó không phải là một so sánh công bằng: tất nhiên C ++ dài dòng hơn Python, nhưng đó không phải vì sự khác biệt trong hệ thống loại của họ, mà là do sự khác biệt về ngữ pháp của họ. Nếu bạn chỉ muốn giảm số lượng ký tự trong nguồn chương trình của mình, hãy tìm hiểu J hoặc APL: Tôi đảm bảo chúng sẽ ngắn hơn. Một so sánh công bằng hơn sẽ là so sánh Python với Haskell. (Đối với bản ghi: Tôi yêu Python và thích nó hơn C ++, nhưng tôi thích Haskell hơn nữa.)
Daniel Pryden

Câu trả lời:


50

Vì bạn đã hỏi một ví dụ cụ thể, tôi sẽ cung cấp cho bạn một ví dụ.

ORM Massive của Rob Conery là 400 dòng mã. Nó nhỏ như vậy vì Rob có thể ánh xạ các bảng SQL và cung cấp kết quả đối tượng mà không yêu cầu nhiều kiểu tĩnh để phản chiếu các bảng SQL. Điều này được thực hiện bằng cách sử dụng dynamickiểu dữ liệu trong C #. Trang web của Rob mô tả quá trình này một cách chi tiết, nhưng có vẻ như rõ ràng rằng, trong trường hợp sử dụng cụ thể này, việc gõ động phần lớn chịu trách nhiệm cho sự ngắn gọn của mã.

So sánh với Dapper của Sam Saffron , sử dụng các loại tĩnh; các SQLMapperlớp học một mình là 3000 dòng mã.

Lưu ý rằng các khuyến cáo thông thường áp dụng, và số dặm của bạn có thể thay đổi; Dapper có những mục tiêu khác với Massive. Tôi chỉ nêu ra đây là một ví dụ về một cái gì đó mà bạn có thể làm trong 400 dòng mã mà có lẽ sẽ không thể thực hiện được nếu không gõ động.


Gõ động cho phép bạn trì hoãn các quyết định loại của mình với thời gian chạy. Đó là tất cả.

Cho dù bạn sử dụng ngôn ngữ gõ động hay ngôn ngữ gõ tĩnh, các lựa chọn loại của bạn vẫn phải hợp lý. Bạn sẽ không thêm hai chuỗi với nhau và mong đợi một câu trả lời bằng số trừ khi các chuỗi chứa dữ liệu số và nếu không, bạn sẽ nhận được kết quả không mong muốn. Một ngôn ngữ gõ tĩnh sẽ không cho phép bạn làm điều này ở nơi đầu tiên.

Những người đề xuất các ngôn ngữ kiểu tĩnh chỉ ra rằng trình biên dịch có thể thực hiện một số lượng đáng kể "kiểm tra độ tỉnh táo" mã của bạn tại thời điểm biên dịch, trước khi một dòng duy nhất thực thi. Đây là một điều tốt ™.

C # có dynamictừ khóa, cho phép bạn trì hoãn quyết định loại đối với thời gian chạy mà không làm mất các lợi ích của an toàn loại tĩnh trong phần còn lại của mã. Kiểu suy luận ( var) giúp loại bỏ phần lớn nỗi đau của việc viết bằng ngôn ngữ gõ tĩnh bằng cách loại bỏ yêu cầu luôn luôn khai báo rõ ràng các loại.


Các ngôn ngữ động dường như ủng hộ một cách tiếp cận tương tác hơn, ngay lập tức để lập trình. Không ai mong bạn phải viết một lớp và trải qua một chu trình biên dịch để gõ một chút mã Lisp và xem nó thực thi. Tuy nhiên, đó chính xác là những gì tôi dự kiến ​​sẽ làm trong C #.


22
Nếu tôi thêm hai chuỗi số với nhau, tôi vẫn không mong đợi kết quả số.
pdr

22
@Robert Tôi đồng ý với hầu hết câu trả lời của bạn. Tuy nhiên, lưu ý rằng có các ngôn ngữ được nhập tĩnh với các vòng lặp đọc-in tương tác, chẳng hạn như Scala và Haskell. Có thể C # không phải là một ngôn ngữ tương tác đặc biệt.
Andres F.

14
Phải học cho tôi một Haskell.
Robert Harvey

7
@RobertHarvey: Bạn có thể ngạc nhiên / ấn tượng với F # nếu bạn chưa thử nó. Bạn nhận được tất cả các loại an toàn (thời gian biên dịch) mà bạn thường có trong ngôn ngữ .NET, ngoại trừ việc bạn hiếm khi phải khai báo bất kỳ loại nào. Kiểu suy luận trong F # vượt xa những gì có sẵn / hoạt động trong C #. Ngoài ra: tương tự như những gì mà Andres và Daniel đang chỉ ra, tương tác F # là một phần của Visual Studio ...
Steven Evers

8
"Bạn sẽ không thêm hai chuỗi với nhau và mong đợi một câu trả lời bằng số trừ khi các chuỗi chứa dữ liệu số và nếu không, bạn sẽ nhận được kết quả không mong muốn" xin lỗi, điều này không liên quan gì đến việc gõ động vàtĩnh , đây là mạnh mẽ so vớiyếu .
vartec

26

Các cụm từ như "gõ tĩnh" và "gõ động" được đưa ra rất nhiều và mọi người có xu hướng sử dụng các định nghĩa khác nhau một cách tinh tế, vì vậy hãy bắt đầu bằng cách làm rõ ý nghĩa của chúng tôi.

Xem xét một ngôn ngữ có các loại tĩnh được kiểm tra tại thời gian biên dịch. Nhưng nói rằng một lỗi loại chỉ tạo ra một cảnh báo không gây tử vong và trong thời gian chạy, mọi thứ đều được gõ. Các loại tĩnh này chỉ để thuận tiện cho người lập trình và không ảnh hưởng đến codegen. Điều này minh họa rằng việc gõ tĩnh không tự nó áp đặt bất kỳ giới hạn nào và không loại trừ lẫn nhau với kiểu gõ động. (Mục tiêu-C rất giống như thế này.)

Nhưng hầu hết các hệ thống kiểu tĩnh không hành xử theo cách này. Có hai thuộc tính chung của các hệ thống loại tĩnh có thể áp đặt các giới hạn:

Trình biên dịch có thể từ chối một chương trình có lỗi loại tĩnh.

Đây là một hạn chế vì nhiều chương trình an toàn loại nhất thiết phải có lỗi loại tĩnh.

Ví dụ: tôi có một tập lệnh Python cần chạy cả Python 2 và Python 3. Một số hàm đã thay đổi loại tham số giữa Python 2 và 3, vì vậy tôi có mã như thế này:

if sys.version_info[0] == 2:
    wfile.write(txt)
else:
    wfile.write(bytes(txt, 'utf-8'))

Trình kiểm tra kiểu tĩnh Python 2 sẽ từ chối mã Python 3 (và ngược lại), mặc dù nó sẽ không bao giờ được thực thi. Chương trình an toàn loại của tôi có lỗi loại tĩnh.

Một ví dụ khác, hãy xem xét một chương trình Mac muốn chạy trên OS X 10.6, nhưng tận dụng các tính năng mới trong 10.7. Các phương thức 10.7 có thể tồn tại hoặc không tồn tại trong thời gian chạy và theo tôi, lập trình viên, để phát hiện ra chúng. Trình kiểm tra loại tĩnh buộc phải từ chối chương trình của tôi để đảm bảo an toàn loại hoặc chấp nhận chương trình, cùng với khả năng tạo ra lỗi loại (thiếu chức năng) khi chạy.

Kiểm tra kiểu tĩnh giả định rằng môi trường thời gian chạy được mô tả đầy đủ bằng thông tin thời gian biên dịch. Nhưng dự đoán tương lai là nguy hiểm!

Đây là một hạn chế nữa:

Trình biên dịch có thể tạo mã giả sử kiểu thời gian chạy là kiểu tĩnh.

Giả sử các loại tĩnh là "chính xác" cung cấp nhiều cơ hội để tối ưu hóa, nhưng những tối ưu hóa này có thể bị hạn chế. Một ví dụ điển hình là các đối tượng proxy, ví dụ từ xa. Giả sử bạn muốn có một đối tượng proxy cục bộ chuyển tiếp các yêu cầu phương thức đến một đối tượng thực trong một quy trình khác. Sẽ thật tuyệt nếu proxy là chung chung (vì vậy nó có thể giả trang thành bất kỳ đối tượng nào) và trong suốt (để mã hiện tại không cần biết nó đang nói chuyện với proxy). Nhưng để làm điều này, trình biên dịch không thể tạo mã giả sử các kiểu tĩnh là chính xác, ví dụ: bằng cách gọi các phương thức nội tuyến tĩnh, bởi vì điều đó sẽ thất bại nếu đối tượng thực sự là một proxy.

Các ví dụ về việc từ xa như vậy trong hành động bao gồm NSXPCConnection của ObjC hoặc trong suốt của C # (việc thực hiện cần một vài sự bi quan trong thời gian chạy - xem tại đây để thảo luận).

Khi codegen không phụ thuộc vào các loại tĩnh và bạn có các phương tiện như chuyển tiếp tin nhắn, bạn có thể thực hiện nhiều nội dung thú vị với các đối tượng proxy, gỡ lỗi, v.v.

Vì vậy, đó là một mẫu của một số thứ bạn có thể làm nếu bạn không bắt buộc phải đáp ứng trình kiểm tra loại. Các giới hạn không được áp đặt bởi các loại tĩnh, mà bằng cách kiểm tra loại tĩnh được thi hành.


2
"Trình kiểm tra loại tĩnh Python 2 sẽ từ chối mã Python 3 (và ngược lại), mặc dù nó sẽ không bao giờ được thực thi. Chương trình an toàn loại của tôi có lỗi loại tĩnh." Âm thanh giống như những gì bạn thực sự cần có một loại "tĩnh nếu", trong đó trình biên dịch / trình thông dịch thậm chí không nhìn thấy mã nếu điều kiện sai.
David Stone

@davidstone Điều đó tồn tại trong c ++
Milind R

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. Trong bất kỳ ngôn ngữ tĩnh hợp lý nào, bạn có thể thực hiện điều này với một IFDEFcâu lệnh tiền xử lý kiểu, trong khi vẫn duy trì an toàn kiểu trong cả hai trường hợp.
Mason Wheeler

1
@MasonWheeler, davidstone Không, các thủ thuật tiền xử lý và static_if đều quá tĩnh. Trong ví dụ của tôi, tôi đã sử dụng Python2 và Python3, nhưng nó có thể dễ dàng như AmazingModule2.0 và AmazingModule3.0, trong đó một số giao diện thay đổi giữa các phiên bản. Sớm nhất mà bạn có thể biết giao diện là vào thời gian nhập mô-đun, điều này nhất thiết là trong thời gian chạy (ít nhất là nếu bạn có bất kỳ mong muốn hỗ trợ liên kết động).
nực

18

Biến vịt gõ là điều đầu tiên mọi người nghĩ đến, nhưng trong hầu hết các trường hợp, bạn có thể nhận được lợi ích tương tự thông qua suy luận kiểu tĩnh.

Nhưng gõ vịt trong các bộ sưu tập được tạo động rất khó để đạt được bằng bất kỳ cách nào khác:

>>> d = JSON.parse(foo)
>>> d['bar'][3]
12
>>> d['baz']['qux']
'quux'

Vì vậy, loại nào JSON.parsetrở lại? Một từ điển của các mảng số nguyên hoặc từ điển của chuỗi? Không, thậm chí điều đó không đủ chung.

JSON.parsephải trả về một số loại "giá trị biến thể" có thể là null, bool, float, chuỗi, mảng của bất kỳ loại nào trong số các loại này theo cách đệ quy hoặc từ điển từ chuỗi sang bất kỳ loại nào theo cách đệ quy. Những điểm mạnh chính của kiểu gõ động đến từ việc có các loại biến thể như vậy.

Cho đến nay, đây là một lợi ích của các loại động , không phải của các ngôn ngữ được gõ động. Một ngôn ngữ tĩnh tốt có thể mô phỏng bất kỳ loại nào như vậy một cách hoàn hảo. (Và thậm chí các ngôn ngữ "xấu" thường có thể mô phỏng chúng bằng cách phá vỡ sự an toàn của loại dưới mui xe và / hoặc yêu cầu cú pháp truy cập vụng về.)

Ưu điểm của ngôn ngữ gõ động là các loại như vậy không thể được suy ra bởi các hệ thống suy luận kiểu tĩnh. Bạn phải viết các loại rõ ràng. Nhưng trong nhiều trường hợp như vậy, bao gồm cả điều này một lần, mã này để mô tả loại chính xác phức tạp như mã để phân tích / xây dựng các đối tượng mà không mô tả loại, do đó vẫn không nhất thiết là một lợi thế.


21
Ví dụ phân tích cú pháp JSON của bạn có thể được xử lý tĩnh bằng Kiểu dữ liệu đại số.

2
OK, câu trả lời của tôi không đủ rõ ràng; cảm ơn. Đó là JSValue là một định nghĩa rõ ràng về một loại động, chính xác là những gì tôi đã nói về. Đó là những kiểu động rất hữu ích, không phải ngôn ngữ yêu cầu gõ động. Tuy nhiên, vẫn có liên quan rằng các loại động không thể được tạo tự động bởi bất kỳ hệ thống suy luận kiểu thực nào, trong khi hầu hết các ví dụ phổ biến mà mọi người không thể hiểu được. Tôi hy vọng phiên bản mới giải thích nó tốt hơn.
abarnert

4
Các loại dữ liệu đại số @MattFenwick bị hạn chế khá nhiều đối với các ngôn ngữ chức năng (trong thực tế). Còn các ngôn ngữ như Java và c # thì sao?
tuần hoàn

4
Các ADT tồn tại trong C / C ++ dưới dạng các hiệp hội được gắn thẻ. Đây không phải là duy nhất cho các ngôn ngữ chức năng.
Clark Gaebel

2
@spirc bạn có thể mô phỏng các ADT bằng ngôn ngữ OO cổ điển bằng cách sử dụng nhiều lớp, tất cả đều xuất phát từ một giao diện chung, các cuộc gọi trong thời gian chạy tới getClass () hoặc GetType () và kiểm tra đẳng thức. Hoặc bạn có thể sử dụng công văn kép, nhưng tôi nghĩ rằng điều đó sẽ mang lại nhiều hơn trong C ++. Vì vậy, bạn có thể có giao diện JSObject và các lớp JSString, JSNumber, JSHash và JSArray. Sau đó, bạn sẽ cần một số mã để biến cấu trúc dữ liệu "chưa được xử lý" này thành cấu trúc dữ liệu "gõ ứng dụng". Nhưng có lẽ bạn cũng muốn làm điều này bằng một ngôn ngữ được gõ động.
Daniel Yankowsky

12

Vì mọi hệ thống kiểu tĩnh thực tế từ xa bị hạn chế nghiêm trọng so với ngôn ngữ lập trình mà nó có liên quan, nó không thể diễn tả tất cả các bất biến mà mã có thể kiểm tra khi chạy. Để không phá vỡ sự bảo đảm mà một hệ thống loại cố gắng đưa ra, do đó, nó không phải là trường hợp sử dụng bảo thủ và không cho phép vượt qua các kiểm tra này, nhưng không thể (trong hệ thống loại) được chứng minh.

Tôi sẽ làm một ví dụ. Giả sử bạn triển khai một mô hình dữ liệu đơn giản để mô tả các đối tượng dữ liệu, các bộ sưu tập của chúng, v.v., được gõ tĩnh theo nghĩa, nếu mô hình nói thuộc tính xcủa đối tượng loại Foo giữ một số nguyên, nó phải luôn giữ một số nguyên. Bởi vì đây là cấu trúc thời gian chạy, bạn không thể gõ tĩnh. Giả sử bạn lưu trữ dữ liệu được mô tả trong các tệp YAML. Bạn tạo một bản đồ băm (sẽ được trao cho thư viện YAML sau này), lấy xthuộc tính, lưu trữ nó trong bản đồ, lấy thuộc tính khác mà thực sự là một chuỗi, ... giữ một giây? Kiểu gì the_map[some_key]bây giờ? Chà, chúng ta biết đó some_key'x'kết quả và do đó phải là một số nguyên, nhưng hệ thống loại thậm chí không thể bắt đầu lý do về điều này.

Một số hệ thống loại được nghiên cứu tích cực có thể hoạt động cho ví dụ cụ thể này, nhưng chúng cực kỳ phức tạp (cả cho người viết trình biên dịch để thực hiện và cho lập trình viên lý do), đặc biệt đối với một cái gì đó "đơn giản" (ý tôi là, tôi chỉ giải thích nó trong một đoạn văn).

Tất nhiên, giải pháp ngày nay là đấm bốc mọi thứ và sau đó đúc (hoặc có một loạt các phương pháp overriden, hầu hết trong số đó đưa ra các ngoại lệ "không được thực hiện"). Nhưng điều này không được gõ tĩnh, đó là một hack xung quanh hệ thống loại để thực hiện kiểm tra loại khi chạy.


Các loại chung không có yêu cầu đấm bốc.
Robert Harvey

@RobertHarvey Có. Tôi đã không nói chuyện với các quyền Anh trong Java C #, tôi đã nói về "quấn nó trong một số lớp wrapper có mục đích duy nhất là đại diện cho một giá trị của T trong một subtype của U". Đa hình tham số (cái mà bạn gọi là gõ chung) tuy nhiên không áp dụng cho ví dụ của tôi. Đó là một sự trừu tượng hóa thời gian biên dịch trên các loại cụ thể, nhưng chúng ta cần một cơ chế gõ thời gian chạy.

Có thể đáng để chỉ ra rằng hệ thống loại Scala đã hoàn thành. Vì vậy, hệ thống loại có thể ít tầm thường hơn bạn hình ảnh.
Andrea

@Andrea Tôi cố tình không giảm mô tả của mình để hoàn thiện. Bao giờ được lập trình trong một tarpit turing? Hoặc cố gắng mã hóa những thứ này trong các loại? Tại một số điểm, nó trở nên quá phức tạp để có thể khả thi.

@del Nam Mình đồng ý. Tôi chỉ chỉ ra rằng các hệ thống loại có thể làm những thứ khá phức tạp. Tôi có ấn tượng rằng câu trả lời của bạn có nghĩa là hệ thống loại chỉ có thể xác minh tầm thường, nhưng trong một lần đọc thứ hai, bạn đã không viết bất cứ điều gì như thế này!
Andrea

7

Bạn không thể làm gì với kiểu gõ động mà bạn không thể làm với kiểu gõ tĩnh, bởi vì bạn có thể thực hiện kiểu gõ động trên đầu ngôn ngữ gõ tĩnh.

Một ví dụ ngắn trong Haskell:

data Data = DString String | DInt Int | DDouble Double

-- defining a '+' operator here, with explicit promotion behavior
DString a + DString b = DString (a ++ b)
DString a + DInt b = DString (a ++ show b)
DString a + DDouble b = DString (a ++ show b)
DInt a + DString b = DString (show a ++ b)
DInt a + DInt b = DInt (a + b)
DInt a + DDouble b = DDouble (fromIntegral a + b)
DDouble a + DString b = DString (show a ++ b)
DDouble a + DInt b = DDouble (a + fromIntegral b)
DDouble a + DDouble b = DDouble (a + b)

Với đủ trường hợp, bạn có thể thực hiện bất kỳ hệ thống loại động nhất định.

Ngược lại, bạn cũng có thể dịch bất kỳ chương trình gõ tĩnh nào thành chương trình động tương đương. Tất nhiên, bạn sẽ mất tất cả các đảm bảo về thời gian biên dịch chính xác mà ngôn ngữ gõ tĩnh cung cấp.

Chỉnh sửa: Tôi muốn giữ đơn giản, nhưng dưới đây là chi tiết hơn về một mô hình đối tượng

Hàm lấy danh sách Dữ liệu làm đối số và thực hiện các phép tính với các hiệu ứng phụ trong ImplMonad và trả về Dữ liệu.

type Function = [Data] -> ImplMonad Data

DMember là một giá trị thành viên hoặc một chức năng.

data DMember = DMemValue Data | DMemFunction Function

Mở rộng Datađể bao gồm các đối tượng và chức năng. Đối tượng là danh sách các thành viên được đặt tên.

data Data = .... | DObject [(String, DMember)] | DFunction Function

Các kiểu tĩnh này đủ để thực hiện mọi hệ thống đối tượng được gõ động mà tôi quen thuộc.


Điều này hoàn toàn không giống nhau vì bạn không thể thêm các loại mới mà không cần xem lại định nghĩa Data.
Jed

5
Bạn đang trộn các khái niệm về gõ động với gõ yếu trong ví dụ của bạn. Gõ động là về hoạt động trên các loại không xác định, không xác định danh sách các loại được phép và hoạt động quá tải giữa các loại.
hcalves

2
@Jed Một khi bạn đã thực hiện mô hình đối tượng, các kiểu cơ bản và các hoạt động nguyên thủy, không cần có nền tảng nào khác. Bạn có thể dễ dàng và tự động dịch các chương trình bằng ngôn ngữ động ban đầu sang phương ngữ này.
NovaDenizen

2
@hcalves Vì bạn đang đề cập đến quá tải trong mã Haskell của tôi, tôi nghi ngờ bạn không có ý tưởng đúng về ngữ nghĩa của nó. Ở đó tôi đã xác định một +toán tử mới kết hợp hai Datagiá trị thành một Datagiá trị khác . Datađại diện cho các giá trị tiêu chuẩn trong hệ thống loại động.
NovaDenizen

1
@Jed: Hầu hết các ngôn ngữ động có một tập hợp nhỏ các loại "nguyên thủy" và một số cách quy nạp để giới thiệu các giá trị mới (cấu trúc dữ liệu như danh sách). Lược đồ, ví dụ, khá xa với ít hơn các nguyên tử, cặp và vectơ. Bạn sẽ có thể thực hiện những điều này theo cùng một cách như phần còn lại của loại động đã cho.
Tikhon Jelvis

3

Màng lọc :

Một màng là một lớp bao quanh toàn bộ đồ thị đối tượng, trái ngược với một lớp bọc chỉ cho một đối tượng. Thông thường, người tạo ra một màng bắt đầu chỉ quấn một vật duy nhất trong màng. Ý tưởng chính là bất kỳ tham chiếu đối tượng nào đi qua màng đều được bọc trong cùng một màng.

nhập mô tả hình ảnh ở đây

Mỗi loại được bao bọc bởi một loại có cùng giao diện, nhưng nó chặn các thông điệp và kết thúc và hủy bỏ các giá trị khi chúng đi qua màng. Loại hàm bao trong ngôn ngữ gõ tĩnh yêu thích của bạn là gì? Có thể Haskell có một loại cho các chức năng đó, nhưng hầu hết các ngôn ngữ được nhập tĩnh không hoặc chúng kết thúc bằng Object → Object, từ bỏ trách nhiệm của chúng là kiểm tra loại một cách hiệu quả.


4
Vâng, Haskell thực sự có thể làm điều này bằng cách sử dụng các loại tồn tại. Nếu bạn có một số loại lớp Foo, bạn có thể tạo một trình bao bọc xung quanh bất kỳ loại nào khởi tạo giao diện đó. class Foo a where ... data Wrapper = forall a. Foo a => Wrapper a
Jake McArthur

@JakeMcArthur, Cảm ơn bạn đã giải thích. Đó là một lý do khác để tôi ngồi xuống và học Haskell.
Mike Samuel

2
Màng của bạn là một "giao diện" và các loại đối tượng được "gõ một cách tồn tại" - nghĩa là chúng ta biết chúng tồn tại dưới giao diện, nhưng đó là tất cả những gì chúng ta biết. Các kiểu tồn tại cho sự trừu tượng hóa dữ liệu đã được biết đến từ những năm 80. Một giới thiệu tốt là cs.cmu.edu/~rwh/plbook/book.pdf chương 21.1
Don Stewart

@DonStewart. Các lớp proxy Java có phải là một cơ chế kiểu tồn tại không? Một nơi mà các màng trở nên khó khăn là trong các ngôn ngữ có hệ thống loại danh nghĩa có tên cho các loại cụ thể có thể nhìn thấy bên ngoài định nghĩa của loại đó. Ví dụ, người ta không thể bọc Stringvì nó là một loại cụ thể trong Java. Smalltalk không có vấn đề này vì nó không cố gắng gõ #doesNotUnderstand.
Mike Samuel

1

Như ai đó đã đề cập, về lý thuyết, bạn không thể làm gì nhiều với kiểu gõ động mà bạn không thể làm với kiểu gõ tĩnh nếu bạn tự thực hiện một số cơ chế nhất định. Hầu hết các ngôn ngữ cung cấp các cơ chế thư giãn kiểu để hỗ trợ tính linh hoạt của kiểu như con trỏ void và loại Đối tượng gốc hoặc giao diện trống.

Câu hỏi tốt hơn là tại sao gõ động phù hợp hơn và phù hợp hơn trong các tình huống và vấn đề nhất định.

Đầu tiên, hãy xác định

Thực thể - Tôi sẽ cần một khái niệm chung về một số thực thể trong mã. Nó có thể là bất cứ thứ gì, từ số nguyên thủy đến dữ liệu phức tạp.

Hành vi - giả sử thực thể của chúng ta có một số trạng thái và một tập hợp các phương thức cho phép thế giới bên ngoài hướng dẫn thực thể đó đến những phản ứng nhất định. Hãy gọi trạng thái + giao diện của thực thể này là hành vi của nó. Một thực thể có thể có nhiều hơn một hành vi được kết hợp theo một cách nhất định bởi ngôn ngữ công cụ cung cấp.

Định nghĩa về các thực thể và hành vi của chúng - mỗi ngôn ngữ cung cấp một số phương tiện trừu tượng giúp bạn xác định các hành vi (tập hợp các phương thức + trạng thái bên trong) của các thực thể nhất định trong chương trình. Bạn có thể gán tên cho các hành vi này và nói rằng tất cả các trường hợp có hành vi này đều thuộc loại nhất định .

Đây có lẽ là một cái gì đó không quen thuộc. Và như bạn nói bạn đã hiểu sự khác biệt, nhưng vẫn còn. Có lẽ không đầy đủ và giải thích chính xác nhất nhưng tôi hy vọng đủ vui để mang lại một số giá trị :)

Gõ tĩnh - hành vi của tất cả các thực thể trong chương trình của bạn được kiểm tra trong thời gian biên dịch, trước khi mã được bắt đầu chạy. Điều này có nghĩa là nếu bạn muốn ví dụ thực thể của bạn thuộc loại Người có hành vi (cư xử như) Magician thì bạn sẽ phải xác định thực thể MagicianPerson và đưa ra hành vi của một pháp sư như throwMagic (). Nếu bạn trong mã của mình, hãy nói nhầm với trình biên dịch Person.throwMagic () thông thường sẽ cho bạn biết"Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".

Gõ động - trong môi trường gõ động, các hành vi có sẵn của các thực thể không được kiểm tra cho đến khi bạn thực sự cố gắng làm điều gì đó với thực thể nhất định. Chạy mã Ruby yêu cầu Person.throwMagic () sẽ không bị bắt cho đến khi mã của bạn thực sự xuất hiện ở đó. Điều này nghe có vẻ bực bội, phải không. Nhưng nó có vẻ khải thị là tốt. Dựa trên tài sản này, bạn có thể làm những điều thú vị. Giống như, giả sử bạn thiết kế một trò chơi trong đó mọi thứ có thể chuyển sang Magician và bạn không thực sự biết ai sẽ là ai, cho đến khi bạn đi đến điểm nhất định trong mã. Và sau đó Frog đến và bạn nóiHeyYouConcreteInstanceOfFrog.include Magicvà từ đó trở đi, chú ếch này trở thành một chú ếch đặc biệt có sức mạnh ma thuật. Ếch khác, vẫn không. Bạn thấy, trong các ngôn ngữ gõ tĩnh, bạn sẽ phải xác định mối quan hệ này bằng một số phương tiện kết hợp hành vi tiêu chuẩn (như thực hiện giao diện). Trong ngôn ngữ gõ động, bạn có thể làm điều đó trong thời gian chạy và không ai quan tâm.

Hầu hết các ngôn ngữ gõ động đều có cơ chế để cung cấp một hành vi chung sẽ bắt bất kỳ thông báo nào được chuyển đến giao diện của chúng. Ví dụ như Ruby method_missingvà PHP __callnếu tôi nhớ tốt. Điều đó có nghĩa là bạn có thể thực hiện bất kỳ loại điều thú vị nào trong thời gian chạy chương trình và đưa ra quyết định loại dựa trên trạng thái chương trình hiện tại. Điều này mang lại các công cụ để mô hình hóa một vấn đề linh hoạt hơn nhiều so với trong, giả sử, ngôn ngữ lập trình tĩnh bảo thủ như Java.

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.