Có một lợi thế thực sự cho các ngôn ngữ năng động? [đóng cửa]


29

Đầu tiên tôi muốn nói Java là ngôn ngữ duy nhất tôi từng sử dụng, vì vậy xin miễn cho sự thiếu hiểu biết của tôi về chủ đề này.

Các ngôn ngữ được nhập động cho phép bạn đặt bất kỳ giá trị nào vào bất kỳ biến nào. Vì vậy, ví dụ bạn có thể viết hàm sau (psuedocode):

void makeItBark(dog){
    dog.bark();
}

Và bạn có thể vượt qua bên trong nó bất cứ giá trị nào. Miễn là giá trị có một bark()phương thức, mã sẽ chạy. Mặt khác, một ngoại lệ thời gian chạy hoặc một cái gì đó tương tự được ném. (Xin hãy sửa tôi nếu tôi sai về điều này).

Dường như, điều này mang lại cho bạn sự linh hoạt.

Tuy nhiên, tôi đã đọc một số ngôn ngữ động và mọi người nói rằng khi thiết kế hoặc viết mã bằng ngôn ngữ động, bạn nghĩ về các loại và đưa chúng vào tài khoản, giống như bạn sử dụng ngôn ngữ gõ tĩnh.

Vì vậy, ví dụ khi viết makeItBark()hàm, bạn dự định chỉ chấp nhận 'những thứ có thể sủa', và bạn vẫn cần đảm bảo rằng bạn chỉ truyền những loại này vào đó. Sự khác biệt duy nhất là bây giờ trình biên dịch sẽ không cho bạn biết khi bạn mắc lỗi.

Chắc chắn, có một lợi thế cho phương pháp này, đó là trong các ngôn ngữ tĩnh, để đạt được 'chức năng này chấp nhận mọi thứ có thể sủa', bạn cần thực hiện một Barkergiao diện rõ ràng . Tuy nhiên, đây có vẻ như là một lợi thế nhỏ.

Tui bỏ lỡ điều gì vậy? Tôi thực sự đạt được gì khi sử dụng một ngôn ngữ gõ động?


6
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof")). Đối số đó thậm chí không phải là một lớp , nó là một ẩn danh có tên là tuple. Gõ vịt ("nếu nó giống như ...") cho phép bạn thực hiện các giao diện đặc biệt với các hạn chế về cơ bản bằng không và không có cú pháp cú pháp. Bạn có thể làm điều này bằng một ngôn ngữ như Java, nhưng cuối cùng bạn có rất nhiều sự phản ánh lộn xộn. Nếu một hàm trong Java yêu cầu ArrayList và bạn muốn cung cấp cho nó một kiểu bộ sưu tập khác, thì bạn là SOL. Trong con trăn thậm chí không thể đi lên.
Phoshi

2
Loại câu hỏi này đã được hỏi trước đây : ở đây , ở đâyở đây . Cụ thể ví dụ đầu tiên dường như trả lời câu hỏi của bạn. Có lẽ bạn có thể viết lại cụm từ của bạn để làm cho nó khác biệt?
đăng nhập

3
Lưu ý rằng ví dụ trong C ++, bạn có thể có một hàm mẫu hoạt động với bất kỳ loại T nào có bark()phương thức, với trình biên dịch phàn nàn khi bạn chuyển một cái gì đó sai nhưng không phải thực sự khai báo một giao diện có chứa vỏ cây ().
Wilbert

2
@Phoshi Đối số trong Python vẫn phải là một loại cụ thể - ví dụ: nó không thể là một số. Nếu bạn có triển khai các đối tượng đặc biệt của riêng mình, truy xuất các thành viên của nó thông qua một số getMemberchức năng tùy chỉnh , sẽ makeItBarknổ tung vì bạn đã gọi dog.barkthay vì dog.getMember("bark"). Điều làm cho mã hoạt động là mọi người đều đồng ý sử dụng loại đối tượng gốc của Python.
Doval

2
@Phoshi Just because I wrote makeItBark with my own types in mind doesn't mean you can't use yours, wheras in a static language it probably /does/ mean that.Như đã chỉ ra trong câu trả lời của tôi, đây không phải là trường hợp nói chung . Đó là trường hợp của Java và C #, nhưng các ngôn ngữ đó có hệ thống mô-đun và mô-đun bị tê liệt nên chúng không đại diện cho những gì gõ tĩnh có thể làm. Tôi có thể viết một từ hoàn hảo chung makeItBarktrong một số ngôn ngữ được nhập tĩnh, ngay cả những ngôn ngữ không có chức năng như C ++ hoặc D.
Doval

Câu trả lời:


35

Các ngôn ngữ được gõ động là uni-gõ

So sánh các hệ thống loại , không có lợi thế trong việc gõ động. Gõ động là một trường hợp đặc biệt của gõ tĩnh - đó là ngôn ngữ gõ tĩnh trong đó mọi biến có cùng loại. Bạn có thể đạt được điều tương tự trong Java (trừ đi sự đồng nhất) bằng cách làm cho mọi biến đều có kiểu Objectvà có các giá trị "đối tượng" là kiểu Map<String, Object>:

void makeItBark(Object dog) {
    Map<String, Object> dogMap = (Map<String, Object>) dog;
    Runnable bark = (Runnable) dogMap.get("bark");
    bark.run();
}

Vì vậy, ngay cả khi không có sự phản chiếu, bạn có thể đạt được hiệu quả tương tự chỉ trong bất kỳ ngôn ngữ gõ tĩnh nào, thuận tiện cú pháp sang một bên. Bạn không nhận được bất kỳ sức mạnh biểu cảm bổ sung; ngược lại, bạn có sức mạnh biểu cảm ít hơn bởi vì trong một ngôn ngữ được gõ động, bạn bị từ chối khả năng hạn chế các biến đối với một số loại nhất định.

Làm cho một con vịt sủa trong một ngôn ngữ gõ tĩnh

Hơn nữa, một ngôn ngữ gõ tĩnh tốt sẽ cho phép bạn viết mã hoạt động với bất kỳ loại nào có barkhoạt động. Trong Haskell, đây là một loại lớp:

class Barkable a where
    bark :: a -> unit

Điều này thể hiện ràng buộc rằng đối với một số loại ađược coi là Barkable, phải tồn tại một barkhàm lấy giá trị của loại đó và không trả về gì.

Sau đó, bạn có thể viết các hàm chung theo các Barkableràng buộc:

makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)

Điều này nói rằng makeItBarksẽ làm việc cho bất kỳ loại đáp ứng Barkableyêu cầu. Điều này có vẻ tương tự như interfacetrong Java hoặc C # nhưng nó có một lợi thế lớn - các loại không phải xác định trước loại lớp nào chúng đáp ứng. Tôi có thể nói rằng loại đó DuckBarkablebất cứ lúc nào, ngay cả khi đó Ducklà loại bên thứ ba tôi không viết. Trên thực tế, không có vấn đề gì khi người viết Duckkhông viết một barkhàm - tôi có thể cung cấp nó sau khi thực tế khi tôi nói ngôn ngữ Duckthỏa mãn Barkable:

instance Barkable Duck where
    bark d = quack (punch (d))

makeItBark (aDuck)

Điều này nói rằng Ducks có thể sủa, và chức năng vỏ cây của chúng được thực hiện bằng cách đấm vịt trước khi làm cho nó quẫy. Với cách đó, chúng ta có thể gọi makeItBarkvịt.

Standard MLOCamlthậm chí linh hoạt hơn ở chỗ bạn có thể đáp ứng cùng loại lớp theo nhiều cách. Trong các ngôn ngữ này, tôi có thể nói rằng các số nguyên có thể được đặt hàng bằng cách sử dụng thứ tự thông thường và sau đó quay lại và nói rằng chúng cũng có thể được sắp xếp theo cách chia (ví dụ: 10 > 5vì 10 chia hết cho 5). Trong Haskell, bạn chỉ có thể khởi tạo một lớp loại một lần. (Điều này cho phép Haskell để tự động biết rằng nó ok để gọi barktrên một con vịt; trong SML hoặc OCaml bạn phải rõ ràng về bark chức năng bạn muốn, bởi vì có thể có nhiều hơn một.)

Sự đồng tình

Tất nhiên, có sự khác biệt về cú pháp. Mã Python mà bạn đã trình bày ngắn gọn hơn nhiều so với mã Java tương đương mà tôi đã viết. Trong thực tế, sự đồng nhất đó là một phần lớn của sức hấp dẫn của các ngôn ngữ được gõ động. Nhưng suy luận kiểu cho phép bạn viết mã giống như ngôn ngữ được gõ tĩnh, bằng cách giải phóng bạn khỏi việc phải viết rõ ràng các loại của mỗi biến. Một ngôn ngữ được gõ tĩnh cũng có thể cung cấp hỗ trợ riêng cho việc gõ động, loại bỏ tính dài dòng của tất cả các thao tác đúc và ánh xạ (ví dụ: C # 's dynamic).

Các chương trình đúng nhưng không đúng

Để công bằng, gõ tĩnh nhất thiết phải loại trừ một số chương trình đúng về mặt kỹ thuật mặc dù trình kiểm tra loại không thể xác minh được. Ví dụ:

if this_variable_is_always_true:
    return "some string"
else:
    return 6

Hầu hết các ngôn ngữ gõ tĩnh sẽ từ chối iftuyên bố này , mặc dù nhánh khác sẽ không bao giờ xảy ra. Trong thực tế, dường như không ai sử dụng loại mã này - bất cứ điều gì quá thông minh đối với trình kiểm tra loại có thể sẽ khiến những người duy trì mã trong tương lai của bạn nguyền rủa bạn và người thân của bạn. Trường hợp cụ thể, ai đó đã dịch thành công 4 dự án Python nguồn mở sang Haskell , điều đó có nghĩa là họ không làm bất cứ điều gì mà một ngôn ngữ gõ tĩnh tốt không thể biên dịch. Hơn nữa, trình biên dịch đã tìm thấy một vài lỗi liên quan đến loại mà đơn vị kiểm tra không bắt được.

Đối số mạnh nhất tôi từng thấy khi gõ động là macro của Lisp, vì chúng cho phép bạn tùy ý mở rộng cú pháp của ngôn ngữ. Tuy nhiên, Typed Rquet là một phương ngữ được gõ tĩnh của Lisp có macro, vì vậy có vẻ như gõ tĩnh và macro không loại trừ lẫn nhau, mặc dù có lẽ khó thực hiện đồng thời hơn.

Táo và cam

Cuối cùng, đừng quên rằng có sự khác biệt lớn hơn về ngôn ngữ so với chỉ hệ thống loại của chúng. Trước Java 8, thực hiện bất kỳ loại lập trình chức năng nào trong Java thực tế là không thể; một lambda đơn giản sẽ yêu cầu 4 dòng mã lớp ẩn danh. Java cũng không hỗ trợ cho các bộ sưu tập bằng chữ (ví dụ [1, 2, 3]). Cũng có thể có sự khác biệt về chất lượng và tính sẵn có của công cụ (IDE, trình gỡ lỗi), thư viện và hỗ trợ cộng đồng. Khi ai đó tuyên bố là có năng suất cao hơn trong Python hoặc Ruby so với Java, sự khác biệt về tính năng đó cần phải được tính đến. Có một sự khác biệt giữa so sánh các ngôn ngữ với tất cả các loại pin , lõi ngôn ngữhệ thống loại .


2
Bạn quên thuộc tính nguồn của bạn cho đoạn đầu tiên - existentialtype.wordpress.com/2011/03/19/...

2
Re: 1, tôi đã cho rằng nó không quan trọng; Tôi đã giải quyết nó dưới sự đồng tình. Re: 2, mặc dù tôi chưa bao giờ nói rõ ràng, nhưng "tốt", ý tôi là "có suy luận kiểu kỹ lưỡng" và "có một hệ thống mô-đun cho phép bạn khớp mã để nhập chữ ký sau thực tế ", không phải là trả trước như Java / Giao diện của C #. Re 3, trách nhiệm chứng minh thuộc về bạn để giải thích cho tôi cách đưa ra hai ngôn ngữ có cú pháp và tính năng tương đương, một ngôn ngữ được gõ động và ngôn ngữ kia có kiểu suy luận đầy đủ, bạn sẽ không thể viết mã có độ dài bằng nhau .
Doval

3
@MattFenwick Tôi đã chứng minh điều đó - đưa ra hai ngôn ngữ có cùng tính năng, một ngôn ngữ được gõ động và ngôn ngữ khác được nhập tĩnh, sự khác biệt chính giữa chúng sẽ là sự hiện diện của chú thích loại và suy luận kiểu sẽ loại bỏ điều đó. Bất kỳ sự khác biệt nào khác về cú pháp đều hời hợt và bất kỳ sự khác biệt nào về tính năng đều biến sự so sánh thành táo và cam. Đó là vào bạn để cho thấy logic này là sai.
Doval

1
Bạn nên có một cái nhìn về Boo. Nó được gõ tĩnh với kiểu suy luận và có các macro cho phép cú pháp của ngôn ngữ được mở rộng.
Mason Wheeler

1
@Doval: Đúng. BTW, ký hiệu lambda không được sử dụng riêng trong lập trình chức năng: theo như tôi biết, Smalltalk có các khối ẩn danh và Smalltalk là hướng đối tượng như nó có thể nhận được. Vì vậy, thường thì giải pháp là chuyển xung quanh một khối mã ẩn danh với một số tham số, bất kể đây là hàm ẩn danh hay đối tượng ẩn danh với chính xác một phương thức ẩn danh. Tôi nghĩ rằng hai cấu trúc này thể hiện cùng một ý tưởng từ hai quan điểm khác nhau (chức năng và hướng đối tượng).
Giorgio

11

Đây là một vấn đề khó khăn, và khá chủ quan. . của diễn đàn này.)

Đây là quan điểm của tôi về nó: Quan điểm của các ngôn ngữ cấp cao là hạn chế những gì lập trình viên có thể làm với máy tính. Điều này gây ngạc nhiên cho nhiều người, vì họ tin rằng mục đích là cung cấp cho người dùng nhiều sức mạnh hơn và đạt được nhiều hơn . Nhưng vì mọi thứ bạn viết trong Prolog, C ++ hoặc List cuối cùng đều được thực thi dưới dạng mã máy, nên thực sự không thể cung cấp cho người lập trình nhiều sức mạnh hơn ngôn ngữ lắp ráp đã cung cấp.

Quan điểm của một ngôn ngữ cấp cao là giúp người lập trình hiểu mã do chính họ tạo ra tốt hơn và giúp họ làm việc hiệu quả hơn khi làm điều tương tự. Một tên chương trình con dễ nhớ hơn một địa chỉ thập lục phân. Bộ đếm đối số tự động dễ sử dụng hơn chuỗi cuộc gọi ở đây, bạn phải tự lấy số lượng đối số chính xác mà không cần trợ giúp. Một hệ thống loại đi xa hơn và hạn chế loại đối số bạn có thể cung cấp ở một nơi nhất định.

Đây là nơi nhận thức của mọi người khác nhau. Một số người (tôi trong số họ) nghĩ rằng miễn là thói quen kiểm tra mật khẩu của bạn sẽ mong đợi chính xác hai đối số và luôn luôn là một chuỗi theo sau là id số, sẽ rất hữu ích khi khai báo điều này trong mã và được tự động nhắc nhở nếu bạn sau đó quên theo quy tắc đó. Gia công phần mềm giữ sách quy mô nhỏ như vậy cho trình biên dịch giúp giải phóng tâm trí của bạn cho các mối quan tâm cấp cao hơn và giúp bạn thiết kế và kiến ​​trúc hệ thống tốt hơn. Do đó, các hệ thống loại là một chiến thắng ròng: chúng cho phép máy tính làm những gì nó giỏi và con người làm những gì chúng giỏi.

Những người khác nhìn thấy khá khác nhau. Họ không thích bị người biên dịch nói phải làm gì. Họ không thích nỗ lực phía trước để quyết định khai báo kiểu và gõ nó. Họ thích phong cách lập trình khám phá nơi bạn viết mã doanh nghiệp thực tế mà không có kế hoạch sẽ cho bạn biết chính xác loại và đối số sẽ sử dụng ở đâu. Và đối với phong cách lập trình họ sử dụng, điều đó có thể hoàn toàn đúng.

Tôi đang quá đơn giản ở đây, tất nhiên. Kiểm tra loại không được ràng buộc chặt chẽ với khai báo loại rõ ràng; cũng có loại suy luận. Lập trình với các thói quen thực sự có các đối số của các loại khác nhau cho phép những thứ khá khác nhau và rất mạnh mẽ mà không thể có được, chỉ là rất nhiều người không chú ý và đủ kiên định để sử dụng thành công như vậy.

Cuối cùng, việc các ngôn ngữ khác nhau như vậy đều rất phổ biến và không có dấu hiệu chết đi cho bạn thấy rằng mọi người đi về lập trình rất khác nhau. Tôi nghĩ rằng các tính năng ngôn ngữ lập trình phần lớn là về các yếu tố con người - thứ hỗ trợ quá trình ra quyết định của con người tốt hơn - và miễn là mọi người làm việc rất khác nhau, thị trường sẽ cung cấp các giải pháp rất khác nhau đồng thời.


3
Cảm ơn câu trả lời. Bạn nói rằng một số người không thích được trình biên dịch nói phải làm gì. [..] Họ thích phong cách lập trình khám phá nơi bạn viết mã doanh nghiệp thực tế mà không có kế hoạch sẽ cho bạn biết chính xác loại và đối số sẽ sử dụng ở đâu. ' Đây là điều mà tôi không hiểu: lập trình không giống như sự ngẫu hứng âm nhạc. Trong âm nhạc nếu bạn nhấn một nốt sai, nó có thể nghe rất hay. Trong lập trình, nếu bạn chuyển một cái gì đó vào một chức năng không có ở đó, rất có thể bạn sẽ gặp phải những lỗi khó chịu. (tiếp tục trong bình luận tiếp theo).
Manila Cohn

3
Tôi đồng ý, nhưng nhiều người không đồng ý. Và mọi người khá sở hữu về những định kiến ​​về tinh thần của họ, đặc biệt là vì họ thường không biết về chúng. Đó là lý do tại sao các cuộc tranh luận về phong cách lập trình thường suy biến thành tranh luận hoặc đánh nhau, và hiếm khi hữu ích để bắt đầu chúng với những người lạ ngẫu nhiên trên internet.
Kilian Foth

1
Đây là lý do tại sao - đánh giá theo những gì tôi đọc - những người sử dụng ngôn ngữ động đưa các loại vào tài khoản giống như nhiều người sử dụng ngôn ngữ tĩnh. Bởi vì khi bạn viết một hàm, nó được cho là lấy các đối số của một loại cụ thể. Không quan trọng nếu trình biên dịch thực thi điều này hay không. Vì vậy, nó đi xuống để gõ tĩnh giúp bạn với điều này, và gõ động không. Trong cả hai trường hợp, một hàm phải lấy một loại đầu vào cụ thể. Vì vậy, tôi không thấy lợi thế của gõ động là gì. Ngay cả khi bạn thích một 'phong cách lập trình khám phá', bạn vẫn không thể vượt qua bất cứ điều gì bạn muốn vào một chức năng.
Manila Cohn

1
Mọi người thường nói về các loại dự án rất khác nhau (đặc biệt là về quy mô). Logic kinh doanh cho một trang web sẽ rất đơn giản so với hệ thống ERP đầy đủ. Có ít rủi ro hơn khi bạn hiểu sai và lợi thế của việc có thể sử dụng lại một số mã đơn giản là phù hợp hơn. Giả sử tôi có một số mã tạo Pdf (hoặc một số HTML) từ cấu trúc dữ liệu. Bây giờ tôi có một nguồn dữ liệu khác (đầu tiên là JSON từ một số API REST, bây giờ là trình nhập Excel). Trong một ngôn ngữ như Ruby, có thể cực kỳ dễ dàng 'mô phỏng' cấu trúc đầu tiên, 'làm cho nó sủa' và sử dụng lại mã Pdf.
thorsten müller

@Prog: Ưu điểm thực sự của ngôn ngữ động là khi mô tả những thứ thực sự khó với hệ thống kiểu tĩnh. Ví dụ, một hàm trong python có thể là một tham chiếu hàm, lambda, một đối tượng hàm hoặc thần biết những gì và tất cả sẽ hoạt động như nhau. Bạn có thể xây dựng một đối tượng bao bọc một đối tượng khác và tự động gửi các phương thức với chi phí cú pháp bằng không, và mọi chức năng về cơ bản đều có các kiểu tham số hóa. Ngôn ngữ động là tuyệt vời để nhanh chóng hoàn thành công việc.
Phoshi

5

Mã được viết bằng ngôn ngữ động không được ghép nối với hệ thống kiểu tĩnh. Do đó, việc thiếu khớp nối này là một lợi thế so với các hệ thống loại tĩnh kém / không đủ (mặc dù nó có thể là một nhược điểm hoặc bất lợi so với hệ thống loại tĩnh lớn).

Hơn nữa, đối với một ngôn ngữ động, một hệ thống kiểu tĩnh không phải được thiết kế, thực hiện, kiểm tra và bảo trì. Điều này có thể làm cho việc thực hiện đơn giản hơn so với một ngôn ngữ có hệ thống kiểu tĩnh.


2
Mọi người có xu hướng cuối cùng thực hiện lại một hệ thống loại tĩnh cơ bản với các thử nghiệm đơn vị của họ (khi nhắm mục tiêu phạm vi kiểm tra tốt)?
Den

Ngoài ra, bạn có ý nghĩa gì khi "ghép" ở đây? Làm thế nào nó sẽ biểu hiện trong một kiến ​​trúc ví dụ dịch vụ vi mô?
Den

@Den 1) câu hỏi hay, tuy nhiên, tôi cảm thấy rằng nó nằm ngoài phạm vi của OP và câu trả lời của tôi. 2) Tôi có nghĩa là khớp nối theo nghĩa này ; một cách ngắn gọn, các hệ thống loại khác nhau áp đặt các ràng buộc (không tương thích) khác nhau đối với mã được viết bằng ngôn ngữ đó. Xin lỗi, tôi không thể trả lời câu hỏi cuối cùng - Tôi không hiểu điều gì đặc biệt về các dịch vụ vi mô về vấn đề này.

2
@Den: Điểm rất hay: Tôi thường quan sát rằng các bài kiểm tra đơn vị tôi viết bằng các lỗi che phủ Python sẽ bị trình biên dịch bắt gặp trong một ngôn ngữ được gõ tĩnh.
Giorgio

@MattFenwick: Bạn đã viết rằng đó là một lợi thế mà "... đối với một ngôn ngữ động, một hệ thống kiểu tĩnh không phải được thiết kế, thực hiện, thử nghiệm và bảo trì." và Den quan sát thấy rằng bạn thường phải thiết kế và kiểm tra các loại của bạn trực tiếp trong mã của bạn. Vì vậy, nỗ lực không bị loại bỏ mà chuyển từ thiết kế ngôn ngữ sang mã ứng dụng.
Giorgio
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.