Có bất kỳ mẫu thiết kế nào chỉ có thể có trong các ngôn ngữ được gõ động như Python không?


30

Tôi đã đọc một câu hỏi liên quan Có bất kỳ mẫu thiết kế nào không cần thiết trong các ngôn ngữ động như Python không? và ghi nhớ câu nói này trên Wikiquote.org

Điều tuyệt vời về cách gõ động là nó cho phép bạn thể hiện bất cứ điều gì có thể tính toán được. Và các hệ thống loại không - các hệ thống loại thường có thể quyết định và chúng hạn chế bạn trong một tập hợp con. Những người ủng hộ hệ thống kiểu tĩnh nói rằng nó ổn, nó đủ tốt; tất cả các chương trình thú vị mà bạn muốn viết sẽ hoạt động như kiểu loại. Nhưng điều đó thật nực cười - một khi bạn có một hệ thống loại, bạn thậm chí không biết những chương trình thú vị nào ở đó.

--- Đài phát thanh kỹ thuật phần mềm Tập 140: Các loại báo và có thể cắm được với Gilad Bracha

Tôi tự hỏi, có những mẫu hoặc chiến lược thiết kế hữu ích nào, bằng cách sử dụng công thức của trích dẫn, "không hoạt động như các loại"?


3
Tôi đã tìm thấy công văn kép và mẫu Khách truy cập rất khó thực hiện bằng các ngôn ngữ được nhập tĩnh, nhưng có thể dễ dàng thực hiện bằng các ngôn ngữ động. Xem câu trả lời này (và câu hỏi), ví dụ: lập trình
viên.stackexchange.com/a/288153/122079

7
Tất nhiên. Bất kỳ mẫu nào liên quan đến việc tạo các lớp mới trong thời gian chạy, ví dụ. (điều đó cũng có thể có trong Java, nhưng không có trong C ++; có một mức độ năng động trượt).
user253751

1
Nó sẽ phụ thuộc rất nhiều vào mức độ tinh vi của hệ thống của bạn :-) Các ngôn ngữ chức năng thường làm khá tốt việc này.
Bergi

1
Mọi người dường như đang nói về các hệ thống kiểu như Java và C # thay vì Haskell hoặc OCaml. Một ngôn ngữ có hệ thống loại mạnh có thể ngắn gọn như ngôn ngữ động nhưng giữ an toàn cho loại.
Andrew nói Phục hồi Monica

@immibis Điều đó không chính xác. Các hệ thống kiểu tĩnh hoàn toàn có thể tạo các lớp "động" mới trong thời gian chạy. Xem Chương 33 của Cơ sở thực tiễn cho Ngôn ngữ lập trình.
vườn

Câu trả lời:


4

Loại hạng nhất

Gõ động có nghĩa là bạn có các loại hạng nhất: bạn có thể kiểm tra, tạo và lưu trữ các loại khi chạy, bao gồm các loại riêng của ngôn ngữ. Nó cũng có nghĩa là các giá trị được gõ, không phải biến .

Ngôn ngữ được nhập tĩnh có thể tạo mã cũng dựa trên các kiểu động, như gửi phương thức, các lớp kiểu, v.v. nhưng theo cách nhìn chung là vô hình với thời gian chạy. Tốt nhất, họ cung cấp cho bạn một số cách để thực hiện nội tâm. Ngoài ra, bạn có thể mô phỏng các loại dưới dạng giá trị nhưng sau đó bạn có một hệ thống loại động đặc biệt.

Tuy nhiên, các hệ thống loại động hiếm khi chỉ có các loại hạng nhất. Bạn có thể có các biểu tượng hạng nhất, gói hạng nhất, hạng nhất .... mọi thứ. Điều này trái ngược với sự tách biệt nghiêm ngặt giữa ngôn ngữ của trình biên dịch và ngôn ngữ thời gian chạy trong các ngôn ngữ được nhập tĩnh. Những gì trình biên dịch hoặc trình thông dịch có thể làm thời gian chạy cũng có thể làm.

Bây giờ, hãy đồng ý rằng suy luận kiểu là một điều tốt và tôi muốn kiểm tra mã của mình trước khi chạy nó. Tuy nhiên, tôi cũng thích có thể sản xuất và biên dịch mã khi chạy. Và tôi cũng thích tính toán trước mọi thứ vào thời gian biên dịch. Trong một ngôn ngữ gõ động, điều này được thực hiện với cùng một ngôn ngữ. Trong OCaml, bạn có hệ thống loại mô-đun / functor, khác với hệ thống loại chính, khác với ngôn ngữ tiền xử lý. Trong C ++, bạn có ngôn ngữ mẫu không liên quan gì đến ngôn ngữ chính, thường không biết gì về các loại trong khi thực thi. Và điều đó tốt trong ngôn ngữ đó, vì họ không muốn cung cấp thêm.

Cuối cùng, điều đó không thay đổi thực sự những gì loại phần mềm bạn có thể phát triển, nhưng expressivity thay đổi cách bạn phát triển họ và cho dù đó là khó khăn hay không.

Mẫu

Các mẫu dựa trên các kiểu động là các mẫu liên quan đến môi trường động: các lớp mở, gửi đi, cơ sở dữ liệu trong bộ nhớ của các đối tượng, tuần tự hóa, v.v. Những thứ đơn giản như các thùng chứa chung hoạt động vì một vectơ không quên trong thời gian chạy về loại đối tượng mà nó giữ (không cần các loại tham số).

Tôi đã cố gắng giới thiệu nhiều cách mã được đánh giá trong Common Lisp cũng như các ví dụ về các phân tích tĩnh có thể có (đây là SBCL). Ví dụ hộp cát biên dịch một tập hợp con mã Lisp nhỏ được tìm nạp từ một tệp riêng biệt. Để an toàn một cách hợp lý, tôi thay đổi có thể đọc được, chỉ cho phép một tập hợp con các ký hiệu tiêu chuẩn và bọc mọi thứ với thời gian chờ.

;;
;; Fetching systems, installing them, etc. 
;; ASDF and QL provide provide resp. a Make-like facility 
;; and system management inside the runtime: those are
;; not distinct programs.
;; Reflexivity allows to develop dedicated tools: for example,
;; being able to find the transitive reduction of dependencies
;; to parallelize builds. 
;; https://gitlab.common-lisp.net/xcvb/asdf-dependency-grovel
;;
(ql:quickload 'trivial-timeout)

;;
;; Readtables are part of the runtime.
;; See also NAMED-READTABLES.
;;
(defparameter *safe-readtable* (copy-readtable *readtable*))
(set-macro-character #\# nil t *safe-readtable*)
(set-macro-character #\: (lambda (&rest args)
                           (declare (ignore args))
                           (error "Colon character disabled."))
                     nil
                     *safe-readtable*)

;; eval-when is necessary when compiling the whole file.
;; This makes the result of the form available in the compile-time
;; environment. 
(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar +WHITELISTED-LISP-SYMBOLS+ 
    '(+ - * / lambda labels mod rem expt round 
      truncate floor ceiling values multiple-value-bind)))

;;
;; Read-time evaluation #.+WHITELISTED-LISP-SYMBOLS+
;; The same language is used to control the reader.
;;
(defpackage :sandbox
  (:import-from
   :common-lisp . #.+WHITELISTED-LISP-SYMBOLS+)
  (:export . #.+WHITELISTED-LISP-SYMBOLS+))

(declaim (inline read-sandbox))

(defun read-sandbox (stream &key (timeout 3))
  (declare (type (integer 0 10) timeout))
  (trivial-timeout:with-timeout (timeout)
    (let ((*read-eval* nil)
          (*readtable* *safe-readtable*)
          ;;
          ;; Packages are first-class: no possible name collision.
          ;;
          (package (make-package (gensym "SANDBOX") :use '(:sandbox))))
      (unwind-protect
           (let ((*package* package))
             (loop
                with stop = (gensym)
                for read = (read stream nil stop)
                until (eq read stop)
                ;;
                ;; Eval at runtime
                ;;
                for value = (eval read)
                ;;
                ;; Type checking
                ;;
                unless (functionp value)
                do (error "Not a function")
                ;; 
                ;; Compile at run-time
                ;;
                collect (compile nil value)))
        (delete-package package)))))

;;
;; Static type checking.
;; warning: Constant 50 conflicts with its asserted type (MOD 11)
;;
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in :timeout 50)))

;; get it right, this time
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in)))

#| /tmp/plugin.lisp
(lambda (x) (+ (* 3 x) 100))
(lambda (a b c) (* a b))
|#

(read-sandbox-file #P"/tmp/plugin.lisp")

;; 
;; caught COMMON-LISP:STYLE-WARNING:
;;   The variable C is defined but never used.
;;

(#<FUNCTION (LAMBDA (#:X)) {10068B008B}>
 #<FUNCTION (LAMBDA (#:A #:B #:C)) {10068D484B}>)

Không có gì ở trên là "không thể" để làm với các ngôn ngữ khác. Cách tiếp cận trình cắm trong Blender, trong phần mềm âm nhạc hoặc IDE cho các ngôn ngữ được biên dịch tĩnh thực hiện quá trình biên dịch lại nhanh chóng, v.v. Thay vì các công cụ bên ngoài, các ngôn ngữ động ưu tiên các công cụ sử dụng thông tin đã có sẵn. Tất cả những người gọi được biết đến của FOO? Tất cả các lớp con của BAR? tất cả các phương pháp được chuyên môn hóa bởi lớp ZOT? đây là dữ liệu nội bộ. Các loại chỉ là một khía cạnh khác của điều này.


(xem thêm: CFFI )


39

Câu trả lời ngắn: không, vì Turing tương đương.

Câu trả lời dài: Anh chàng này là một kẻ troll. Mặc dù đúng là các hệ thống kiểu "giới hạn bạn trong một tập hợp con", nhưng những thứ bên ngoài tập hợp con đó, theo định nghĩa, là những thứ không hoạt động.

Bất cứ điều gì bạn có thể làm trong bất kỳ ngôn ngữ lập trình hoàn chỉnh Turing nào (ngôn ngữ được thiết kế cho lập trình đa năng, cộng với nhiều thứ không phải là một thanh khá thấp để xóa và có một số ví dụ về hệ thống trở thành Turing- hoàn toàn không chủ ý) bạn có thể thực hiện bằng bất kỳ ngôn ngữ lập trình Turing-Complete nào khác. Điều này được gọi là "Turing tương đương", và nó chỉ có nghĩa chính xác những gì nó nói. Điều quan trọng, điều đó không có nghĩa là bạn có thể làm điều khác dễ dàng như ngôn ngữ kia - một số người sẽ cho rằng đó là toàn bộ quan điểm tạo ra một ngôn ngữ lập trình mới ngay từ đầu: để cho bạn cách làm tốt hơn những thứ mà các ngôn ngữ hiện có hút vào.

Ví dụ, một hệ thống loại động có thể được mô phỏng trên hệ thống loại OO tĩnh bằng cách chỉ khai báo tất cả các biến, tham số và trả về giá trị làm Objectloại cơ sở và sau đó sử dụng phản xạ để truy cập dữ liệu cụ thể bên trong, vì vậy khi bạn nhận ra điều này bạn thấy rằng thực sự không có gì bạn có thể làm trong một ngôn ngữ động mà bạn không thể làm bằng ngôn ngữ tĩnh. Nhưng làm theo cách đó sẽ là một mớ hỗn độn, tất nhiên.

Anh chàng trong đoạn trích dẫn chính xác là các kiểu tĩnh hạn chế những gì bạn có thể làm, nhưng đó là một tính năng quan trọng, không phải là vấn đề. Các dòng trên đường giới hạn những gì bạn có thể làm trong xe của bạn, nhưng bạn có thấy chúng hạn chế, hoặc hữu ích? (Tôi biết rằng tôi sẽ không muốn lái xe trên một con đường phức tạp, bận rộn, nơi không có gì bảo những chiếc xe đi ngược chiều để đi về phía họ và không đi qua nơi tôi đang lái xe!) Bằng cách thiết lập các quy tắc phân định rõ ràng những gì xem xét hành vi không hợp lệ và đảm bảo rằng điều đó sẽ không xảy ra, bạn sẽ giảm đáng kể khả năng xảy ra sự cố khó chịu.

Ngoài ra, anh ấy đã nhầm lẫn ở phía bên kia. Không phải là "tất cả các chương trình thú vị mà bạn muốn viết sẽ hoạt động như các loại", mà là "tất cả các chương trình thú vị mà bạn muốn viết sẽ yêu cầu các loại." Một khi bạn vượt qua một mức độ phức tạp nhất định, sẽ rất khó để duy trì cơ sở mã mà không có hệ thống loại để giữ cho bạn phù hợp, vì hai lý do.

Đầu tiên, bởi vì mã không có chú thích kiểu rất khó đọc. Hãy xem xét Python sau:

def sendData(self, value):
   self.connection.send(serialize(value.someProperty))

Bạn mong đợi dữ liệu trông như thế nào mà hệ thống ở đầu kia của kết nối nhận được? Và nếu nó nhận được một cái gì đó trông hoàn toàn sai, làm thế nào để bạn tìm ra những gì đang xảy ra?

Tất cả phụ thuộc vào cấu trúc của value.someProperty. Nhưng nó trông như thế nào? Câu hỏi hay! Gọi là sendData()gì? Nó đang trôi qua là gì? Biến đó trông như thế nào? Nó từ đâu đến? Nếu nó không phải là cục bộ, bạn phải theo dõi toàn bộ lịch sử valueđể theo dõi những gì đang diễn ra. Có thể bạn đang vượt qua một thứ khác cũng có một somePropertytài sản, nhưng nó không làm những gì bạn nghĩ nó làm?

Bây giờ chúng ta hãy xem nó với các chú thích kiểu, như bạn có thể thấy trong ngôn ngữ Boo, sử dụng cú pháp rất giống nhau nhưng được gõ tĩnh:

def SendData(value as MyDataType):
   self.Connection.Send(Serialize(value.SomeProperty))

Nếu có điều gì đó không ổn, đột nhiên công việc sửa lỗi của bạn trở nên dễ dàng hơn: tìm kiếm định nghĩa MyDataType! Ngoài ra, cơ hội có hành vi xấu vì bạn đã vượt qua một số loại không tương thích cũng có một thuộc tính có cùng tên đột nhiên bằng không, vì hệ thống loại sẽ không cho phép bạn mắc lỗi đó.

Lý do thứ hai được xây dựng dựa trên lý do thứ nhất: trong một dự án lớn và phức tạp, rất có thể bạn có nhiều người đóng góp. (Và nếu không, bạn sẽ tự xây dựng nó trong một thời gian dài, về cơ bản là giống nhau. Hãy thử đọc mã bạn đã viết 3 năm trước nếu bạn không tin tôi!) Điều này có nghĩa là bạn không biết điều gì đã xảy ra đi qua người đứng đầu đã viết gần như bất kỳ phần nào của mã vào thời điểm họ viết nó, bởi vì bạn không ở đó, hoặc không nhớ đó có phải là mã của chính bạn từ lâu không. Có khai báo kiểu thực sự giúp bạn hiểu ý định của mã là gì!

Những người như anh chàng trong đoạn trích dẫn thường xuyên hiểu sai về lợi ích của việc gõ tĩnh như là "giúp trình biên dịch" hoặc "tất cả về hiệu quả" trong một thế giới nơi mà tài nguyên phần cứng gần như không giới hạn làm cho nó ngày càng ít liên quan hơn qua mỗi năm. Nhưng như tôi đã chỉ ra, trong khi những lợi ích đó chắc chắn tồn tại, lợi ích chính là ở các yếu tố con người, đặc biệt là khả năng đọc và bảo trì mã. (Tuy nhiên, hiệu quả được thêm vào chắc chắn là một phần thưởng tuyệt vời!)


24
"Anh chàng này là một kẻ troll." - Tôi không chắc chắn rằng một cuộc tấn công vượn nhân hình sẽ giúp ích cho trường hợp được trình bày tốt của bạn. Và mặc dù tôi nhận thức rõ rằng tranh luận từ chính quyền là một ngụy biện tồi tệ không kém như chủ đề quảng cáo, tôi vẫn muốn chỉ ra rằng Gilad Bracha có lẽ đã thiết kế nhiều ngôn ngữ hơn và (hầu hết phù hợp cho cuộc thảo luận này) nhiều hệ thống kiểu tĩnh hơn hầu hết. Chỉ là một trích đoạn nhỏ: ông là nhà thiết kế duy nhất của Drameak, đồng thiết kế của Dart, đồng tác giả của Đặc tả ngôn ngữ Java và Đặc tả máy ảo Java, đã làm việc về thiết kế Java và JVM, được thiết kế
Jörg W Mittag

10
Strongtalk (một hệ thống kiểu tĩnh cho Smalltalk), hệ thống loại Dart, hệ thống kiểu Fileak, luận án tiến sĩ về mô đun hóa là cơ sở của hầu hết mọi hệ thống mô-đun hiện đại (ví dụ Java 9, ECMAScript 2015, Scala, Dart, Drameak, Ioke , Seph), (các) bài viết của anh ấy về mixins đã cách mạng hóa cách chúng ta nghĩ về chúng. Bây giờ, điều đó không có nghĩa rằng ông là đúng, nhưng tôi làm cho rằng đã thiết kế nhiều hệ thống kiểu tĩnh khiến anh nhiều hơn một chút so với một "troll".
Jörg W Mittag

17
"Trong khi sự thật là các hệ thống kiểu" giới hạn bạn trong một tập hợp con ", thì những thứ bên ngoài tập hợp con đó, theo định nghĩa, là những thứ không hoạt động." - Cái này sai. Chúng tôi biết từ tính không ổn định của vấn đề tạm dừng, Định lý của Rice và vô số các kết quả không thể giải quyết và không tính toán khác mà trình kiểm tra loại tĩnh không thể quyết định cho tất cả các chương trình cho dù chúng là loại an toàn hay loại không an toàn. Nó không thể chấp nhận các chương trình đó (một số trong số đó là không an toàn), vì vậy lựa chọn lành mạnh duy nhất là từ chối chúng (tuy nhiên, một số trong số đó là loại an toàn). Ngoài ra, ngôn ngữ phải được thiết kế trong chương trình
Jörg W Mittag

9
Cách như vậy để làm cho lập trình viên không thể viết những chương trình không thể giải quyết đó, nhưng một lần nữa, một số trong số đó thực sự an toàn. Vì vậy, bất kể bạn cắt nó như thế nào: lập trình viên không được viết các chương trình loại an toàn. Và kể từ đó thực sự là vô cùng nhiều trong số họ (thường), chúng ta có thể gần như chắc chắn rằng ít nhất một số trong số đó là không chỉ những thứ đó không làm việc, nhưng cũng rất hữu ích.
Jörg W Mittag

8
@MasonWheeler: Vấn đề dừng lại xuất hiện mọi lúc, chính xác trong bối cảnh kiểm tra kiểu tĩnh. Trừ khi các ngôn ngữ được thiết kế cẩn thận để ngăn lập trình viên viết một số loại chương trình nhất định, việc kiểm tra kiểu tĩnh nhanh chóng trở nên tương đương với việc giải quyết vấn đề Dừng. Bạn kết thúc với các chương trình bạn không được phép viết vì chúng có thể gây nhầm lẫn cho trình kiểm tra loại hoặc bạn kết thúc với các chương trình bạn được phép viết nhưng chúng mất một lượng thời gian vô hạn để gõ kiểm tra.
Jörg W Mittag

27

Tôi sẽ bước sang phần 'mẫu' vì tôi nghĩ rằng nó phá hủy định nghĩa về cái gì không phải là mẫu và tôi đã mất hứng thú với cuộc tranh luận đó từ lâu. Điều tôi sẽ nói là có những điều bạn có thể làm bằng một số ngôn ngữ mà bạn không thể làm ở những ngôn ngữ khác. Hãy để tôi nói rõ, tôi không nói có những vấn đề bạn có thể giải quyết bằng một ngôn ngữ mà bạn không thể giải quyết bằng ngôn ngữ khác. Mason đã chỉ ra sự hoàn thiện Turing.

Ví dụ, tôi đã viết một lớp bằng python để bọc một phần tử DOM DOM và biến nó thành một đối tượng lớp đầu tiên. Đó là, bạn có thể viết mã:

doc.header.status.text()

và bạn có nội dung của đường dẫn đó từ một đối tượng XML được phân tích cú pháp. loại gọn gàng và ngăn nắp, IMO. Và nếu không có nút đầu, nó chỉ trả về một đối tượng giả không chứa gì ngoài các đối tượng giả (rùa hết cỡ.) Không có cách nào thực sự để làm điều đó, giả sử, Java. Bạn phải biên soạn một lớp trước thời hạn dựa trên một số kiến ​​thức về cấu trúc của XML. Đặt sang một bên cho dù đây là một ý tưởng tốt, loại điều này thực sự thay đổi cách bạn giải quyết vấn đề bằng một ngôn ngữ năng động. Tuy nhiên, tôi không nói rằng nó thay đổi theo cách nhất thiết phải luôn tốt hơn. Có một số chi phí nhất định cho các phương pháp năng động và câu trả lời của Mason đưa ra một cái nhìn tổng quan đúng đắn. Cho dù họ là một lựa chọn tốt phụ thuộc vào nhiều yếu tố.

Bên cạnh đó, bạn có thể làm điều này bằng Java vì bạn có thể xây dựng trình thông dịch python trong Java . Thực tế là việc giải quyết một vấn đề cụ thể bằng một ngôn ngữ nhất định có thể có nghĩa là xây dựng một trình thông dịch hoặc một cái gì đó tương tự với nó thường bị bỏ qua khi mọi người nói về tính hoàn chỉnh của Turing.


4
Bạn không thể làm điều này trong Java vì Java được thiết kế kém. Việc sử dụng C # sẽ không khó lắm IDynamicMetaObjectProvider, và nó thật đơn giản trong Boo. ( Đây là một triển khai trong ít hơn 100 dòng, được bao gồm như một phần của cây nguồn tiêu chuẩn trên GitHub, bởi vì điều đó thật dễ dàng!)
Mason Wheeler

6
@MasonWheeler "IDynamicMetaObjectProvider"? Điều đó có liên quan đến dynamictừ khóa của C # không? ... mà hiệu quả chỉ là gõ vào động gõ vào C #? Không chắc chắn đối số của bạn là hợp lệ nếu tôi đúng.
jpmc26

9
@MasonWheeler Bạn đang đi vào ngữ nghĩa. Không cần tranh luận về các chi tiết vụn vặt (Chúng tôi không phát triển một hình thức toán học về SE ở đây.), Gõ động là thực hành đưa ra các quyết định biên dịch thời gian xung quanh các loại, đặc biệt là xác minh rằng mỗi loại có các thành viên cụ thể mà chương trình truy cập. Đó là mục tiêu dynamichoàn thành trong C #. "Phản ánh và tra cứu từ điển" xảy ra trong thời gian chạy, không phải thời gian biên dịch. Tôi thực sự không chắc chắn làm thế nào bạn có thể tạo ra một trường hợp mà nó không thêm kiểu gõ động vào ngôn ngữ. Quan điểm của tôi là đoạn cuối của Jimmy nói về điều đó.
jpmc26

44
Mặc dù không phải là một fan hâm mộ lớn của Java, tôi cũng dám nói rằng việc gọi Java là "thiết kế kém" đặc biệt bởi vì nó không thêm kiểu gõ động là ... quá nhiệt tình.
jpmc26

5
Ngoài cú pháp thuận tiện hơn một chút, nó khác với từ điển như thế nào?
Theodoros Chatzigiannakis

10

Các trích dẫn là chính xác, nhưng cũng thực sự không lịch sự. Hãy chia nhỏ nó để xem tại sao:

Điều tuyệt vời về cách gõ động là nó cho phép bạn thể hiện bất cứ điều gì có thể tính toán được.

Vâng, không hoàn toàn. Một ngôn ngữ với kiểu gõ động cho phép bạn diễn đạt bất cứ điều gì miễn là nó hoàn thành , đó là hầu hết. Hệ thống loại chính nó không cho phép bạn thể hiện tất cả mọi thứ. Hãy cho anh ta lợi ích của sự nghi ngờ ở đây mặc dù.

Và các hệ thống loại không - các hệ thống loại thường có thể quyết định và chúng hạn chế bạn trong một tập hợp con.

Điều này là đúng, nhưng lưu ý rằng chúng ta hiện đang nói chắc chắn về những gì hệ thống loại cho phép, chứ không phải những gì ngôn ngữ sử dụng một hệ thống loại cho phép. Mặc dù có thể sử dụng một hệ thống loại để tính toán công cụ tại thời gian biên dịch, nhưng điều này nói chung không phải là Turing hoàn chỉnh (vì hệ thống loại nói chung có thể quyết định được), nhưng hầu như bất kỳ ngôn ngữ gõ tĩnh nào cũng Turing hoàn thành trong thời gian chạy của nó (ngôn ngữ được gõ phụ thuộc không, nhưng tôi không tin rằng chúng ta đang nói về họ ở đây).

Những người ủng hộ hệ thống kiểu tĩnh nói rằng nó ổn, nó đủ tốt; tất cả các chương trình thú vị mà bạn muốn viết sẽ hoạt động như kiểu loại. Nhưng điều đó thật nực cười - một khi bạn có một hệ thống loại, bạn thậm chí không biết những chương trình thú vị nào ở đó.

Vấn đề là các ngôn ngữ kiểu động có một kiểu tĩnh. Đôi khi tất cả mọi thứ là một chuỗi, và phổ biến hơn là có một số liên kết được gắn thẻ trong đó mọi thứ là một túi thuộc tính hoặc giá trị như int hoặc double. Vấn đề là các ngôn ngữ tĩnh cũng có thể làm điều này, về mặt lịch sử, nó hơi vụng về để làm điều này, nhưng các ngôn ngữ gõ tĩnh hiện đại làm cho việc này khá dễ thực hiện như sử dụng ngôn ngữ kiểu động, vì vậy làm thế nào có thể có sự khác biệt trong những gì lập trình viên có thể xem là một chương trình thú vị? Các ngôn ngữ tĩnh có chính xác các hiệp hội được gắn thẻ cũng như các loại khác.

Để trả lời câu hỏi trong tiêu đề: Không, không có mẫu thiết kế nào không thể được triển khai bằng ngôn ngữ được nhập tĩnh, bởi vì bạn luôn có thể triển khai đủ hệ thống động để có được chúng. Có thể có các mẫu mà bạn nhận được 'miễn phí' bằng ngôn ngữ động; điều này có thể hoặc không đáng để đưa ra những nhược điểm của các ngôn ngữ đó đối với YMMV .


2
Tôi không hoàn toàn chắc chắn liệu bạn chỉ trả lời có hay không. Âm thanh giống như không với tôi.
user7610

1
@TheodorosChatzigiannakis Vâng, làm thế nào khác ngôn ngữ động sẽ được thực hiện? Đầu tiên, bạn sẽ vượt qua cho một kiến ​​trúc sư phi hành gia nếu bạn muốn thực hiện một hệ thống lớp động hoặc bất cứ điều gì khác một chút liên quan. Thứ hai, có lẽ bạn không có tài nguyên để làm cho nó có thể gỡ lỗi, hoàn toàn hướng nội, trình diễn ("chỉ sử dụng từ điển" là cách các ngôn ngữ chậm được triển khai). Thứ ba, một số tính năng động được sử dụng tốt nhất khi được tích hợp trong toàn bộ ngôn ngữ, không chỉ là một thư viện: nghĩ thu gom rác thải ví dụ (có tổng công ty như thư viện, nhưng họ không thường được sử dụng).
coredump

1
@Theodoros Theo bài báo tôi đã liên kết ở đây một lần, tất cả trừ 2,5% cấu trúc (trong các mô-đun Python mà các nghiên cứu đã xem) có thể dễ dàng diễn đạt bằng ngôn ngữ đánh máy. Có thể 2,5% làm cho việc trả chi phí đánh máy động đáng giá. Đó thực chất là những gì câu hỏi của tôi là về. neverworkintheory.org/2016/06/13/polymorphism-in-python.html
user7610

3
@JiriDanek Theo như tôi có thể nói, không có gì ngăn cản một ngôn ngữ gõ tĩnh có các điểm gọi đa hình và duy trì gõ tĩnh trong quá trình này. Xem Kiểm tra loại tĩnh của đa phương thức . Có lẽ tôi đang hiểu nhầm liên kết của bạn.
Theodoros Chatzigiannakis

1
"Một ngôn ngữ với kiểu gõ động cho phép bạn diễn đạt bất cứ điều gì miễn là nó hoàn chỉnh, hầu hết là như vậy." Mặc dù đây là một tuyên bố đúng, nhưng nó không thực sự giữ trong "thế giới thực" bởi vì số lượng văn bản mà người ta có để viết có thể rất lớn.
Daniel Jour

4

Chắc chắn có những điều bạn chỉ có thể làm trong các ngôn ngữ được gõ động. Nhưng họ không nhất thiết phải tốt thiết kế .

Trước tiên, bạn có thể gán một số nguyên 5 sau đó là một chuỗi 'five'hoặc một Catđối tượng cho cùng một biến. Nhưng bạn chỉ làm cho người đọc mã của bạn khó khăn hơn để tìm hiểu điều gì đang xảy ra, mục đích của mỗi biến là gì.

Bạn có thể thêm một phương thức mới vào một lớp thư viện Ruby và truy cập các trường riêng của nó. Có thể có trường hợp một vụ hack như vậy có thể hữu ích nhưng điều này sẽ vi phạm đóng gói. (Tôi không ngại việc thêm các phương thức chỉ dựa vào giao diện công cộng, nhưng đó không phải là điều mà các phương thức mở rộng C # được nhập tĩnh không thể làm được.)

Bạn có thể thêm một trường mới vào một đối tượng của lớp người khác để truyền một số dữ liệu bổ sung xung quanh nó. Nhưng đó là thiết kế tốt hơn để tạo cấu trúc mới hoặc mở rộng kiểu ban đầu.

Nói chung, bạn muốn mã của mình càng có tổ chức, bạn càng có ít lợi thế hơn từ việc có thể thay đổi động các định nghĩa kiểu hoặc gán các giá trị của các loại khác nhau cho cùng một biến. Nhưng sau đó, mã của bạn không khác với những gì bạn có thể đạt được trong một ngôn ngữ gõ tĩnh.

Những ngôn ngữ năng động là tốt đường cú pháp. Ví dụ, khi đọc một đối tượng JSON được giải tuần tự, bạn có thể tham khảo một giá trị lồng nhau đơn giản là obj.data.article[0].content- gọn gàng hơn nhiều so với nói obj.getJSONObject("data").getJSONArray("article").getJSONObject(0).getString("content").

Các nhà phát triển Ruby đặc biệt có thể nói rất nhiều về phép thuật có thể đạt được bằng cách triển khai method_missing, đây là một phương pháp cho phép bạn xử lý các cuộc gọi đã thử với các phương thức không được khai báo. Ví dụ: ActiveRecord ORM sử dụng nó để bạn có thể thực hiện cuộc gọi User.find_by_email('joe@example.com')mà không cần khai báo find_by_emailphương thức. Tất nhiên, không có gì là không thể đạt được như UserRepository.FindBy("email", "joe@example.com")trong một ngôn ngữ được gõ tĩnh, nhưng bạn không thể phủ nhận sự gọn gàng của nó.


4
Chắc chắn có những điều bạn chỉ có thể làm trong các ngôn ngữ gõ tĩnh. Nhưng chúng không nhất thiết phải là thiết kế tốt.
coredump

2
Điểm về đường cú pháp có rất ít liên quan đến kiểu gõ động và mọi thứ với, tốt, cú pháp.
rẽ trái

@leftaroundabout Các mẫu có mọi thứ để làm với cú pháp. Loại hệ thống cũng có rất nhiều để làm với nó.
dùng253751

4

Mẫu Proxy động là một lối tắt để triển khai các đối tượng proxy mà không cần một lớp cho mỗi loại bạn cần proxy.

class Proxy(object):
    def __init__(self, obj):
        self.__target = obj

    def __getattr__(self, attr):
        return getattr(self.__target, attr)

Sử dụng điều này, Proxy(someObject)tạo ra một đối tượng mới hoạt động giống như someObject. Rõ ràng bạn cũng sẽ muốn thêm chức năng bổ sung bằng cách nào đó, nhưng đây là một cơ sở hữu ích để bắt đầu. Trong một ngôn ngữ tĩnh hoàn chỉnh, bạn cần viết một lớp Proxy cho mỗi loại bạn muốn proxy hoặc sử dụng tạo mã động (được thừa nhận, được bao gồm trong thư viện chuẩn của nhiều ngôn ngữ tĩnh, phần lớn là do các nhà thiết kế của họ biết các vấn đề không thể làm nguyên nhân này).

Một trường hợp sử dụng khác của ngôn ngữ động được gọi là "vá khỉ". Theo nhiều cách, đây là một mô hình chống chứ không phải là một mô hình, nhưng nó có thể được sử dụng theo những cách hữu ích nếu được thực hiện cẩn thận. Và trong khi không có do lý thuyết nào thì việc vá khỉ không thể được thực hiện bằng ngôn ngữ tĩnh, tôi chưa bao giờ thấy một ngôn ngữ nào thực sự có nó.


Tôi nghĩ rằng tôi có thể mô phỏng điều này trong Go. Có một tập hợp các phương thức mà tất cả các đối tượng được ủy quyền phải có (nếu không, con vịt có thể không quẫy và tất cả rơi ra ngoài). Tôi có thể tạo giao diện Go với các phương thức này. Tôi sẽ phải suy nghĩ về nó nhiều hơn, nhưng tôi nghĩ những gì tôi có trong đầu sẽ hoạt động.
user7610

Bạn có thể một cái gì đó tương tự trong bất kỳ ngôn ngữ .NET nào với RealProxy và generic.
LittleEwok

@LittleEwok - RealProxy sử dụng việc tạo mã thời gian chạy - như tôi nói, nhiều ngôn ngữ tĩnh hiện đại có cách giải quyết như thế này, nhưng ngôn ngữ động vẫn dễ dàng hơn.
Jules

Các phương pháp mở rộng C # giống như vá khỉ được thực hiện an toàn. Bạn không thể thay đổi các phương thức hiện có, nhưng bạn có thể thêm các phương thức mới.
Andrew nói Phục hồi Monica

3

Vâng , có nhiều mẫu và kỹ thuật chỉ có thể có trong một ngôn ngữ được gõ động.

Khỉ vá là một kỹ thuật trong đó các thuộc tính hoặc phương thức được thêm vào các đối tượng hoặc các lớp khi chạy. Kỹ thuật này không thể thực hiện được bằng ngôn ngữ gõ tĩnh vì điều này có nghĩa là các loại và thao tác không thể được xác minh tại thời điểm biên dịch. Hay nói cách khác, nếu một ngôn ngữ hỗ trợ vá khỉ thì đó là định nghĩa một ngôn ngữ động.

Có thể chứng minh rằng nếu một ngôn ngữ hỗ trợ vá khỉ (hoặc các kỹ thuật tương tự để sửa đổi các loại trong thời gian chạy), thì nó không thể được kiểm tra kiểu tĩnh. Vì vậy, nó không chỉ là một giới hạn trong các ngôn ngữ hiện tại, nó là một hạn chế cơ bản của gõ tĩnh.

Vì vậy, trích dẫn là hoàn toàn chính xác - nhiều thứ có thể có trong một ngôn ngữ động hơn là một ngôn ngữ gõ tĩnh. Mặt khác, một số loại phân tích nhất định chỉ có thể có trong một ngôn ngữ gõ tĩnh. Ví dụ, bạn luôn biết các hoạt động nào được phép trên một loại nhất định, cho phép bạn phát hiện các hoạt động bất hợp pháp ở loại biên dịch. Không thể xác minh như vậy trong một ngôn ngữ động khi các hoạt động có thể được thêm hoặc xóa trong thời gian chạy.

Đây là lý do tại sao không có "tốt nhất" rõ ràng trong xung đột giữa ngôn ngữ tĩnh và ngôn ngữ động. Các ngôn ngữ tĩnh từ bỏ sức mạnh nhất định trong thời gian chạy để đổi lấy một loại sức mạnh khác trong thời gian biên dịch, chúng tin rằng làm giảm số lượng lỗi và giúp phát triển dễ dàng hơn. Một số người tin rằng sự đánh đổi là xứng đáng, những người khác thì không.

Các câu trả lời khác đã lập luận tương đương Turing có nghĩa là bất cứ điều gì có thể trong một ngôn ngữ đều có thể có trong tất cả các ngôn ngữ. Nhưng điều này không tuân theo. Để hỗ trợ một cái gì đó như vá khỉ trong một ngôn ngữ tĩnh, về cơ bản bạn phải triển khai một ngôn ngữ phụ động bên trong ngôn ngữ tĩnh. Điều này là tất nhiên có thể, nhưng tôi sẽ cho rằng bạn đang lập trình bằng ngôn ngữ động được nhúng, vì bạn cũng mất kiểm tra kiểu tĩnh tồn tại trong ngôn ngữ máy chủ.

C # kể từ phiên bản 4 đã hỗ trợ các đối tượng gõ động. Rõ ràng các nhà thiết kế ngôn ngữ thấy lợi ích trong việc có cả hai loại gõ có sẵn. Nhưng nó cũng cho thấy bạn không thể ăn bánh của mình và tôi cũng vậy: Khi bạn sử dụng các đối tượng động trong C #, bạn có khả năng làm một việc gì đó như vá khỉ, nhưng bạn cũng mất xác minh kiểu tĩnh để tương tác với các đối tượng này.


+1 đoạn thứ hai của bạn đến đoạn cuối tôi nghĩ là đối số quan trọng. Tôi vẫn tranh luận rằng có một sự khác biệt mặc dù với các loại tĩnh, bạn có toàn quyền kiểm soát vị trí và những gì bạn có thể vá khỉ
jk.

2

Tôi tự hỏi, có những mẫu hoặc chiến lược thiết kế hữu ích nào, bằng cách sử dụng công thức của trích dẫn, "không hoạt động như các loại"?

Có và không.

Có tình huống trong đó lập trình viên biết loại biến có độ chính xác cao hơn trình biên dịch. Trình biên dịch có thể biết rằng một cái gì đó là một Object, nhưng lập trình viên sẽ biết (do sự bất biến của chương trình) rằng nó thực sự là một String.

Hãy để tôi chỉ ra một số ví dụ về điều này:

Map<Class<?>, Function<?, String>> someMap;
someMap.get(object.getClass()).apply(object);

Tôi biết điều đó someMap.get(T.class)sẽ trả về a Function<T, String>, vì cách tôi xây dựng một số Bản đồ. Nhưng Java chỉ chắc chắn rằng tôi đã có Hàm.

Một vi dụ khac:

data = parseJSON(someJson)
validate(data, someJsonSchema);
print(data.properties.rowCount);

Tôi biết rằng data.properIES.rowCount sẽ là một tham chiếu hợp lệ và là một số nguyên, bởi vì tôi đã xác thực dữ liệu theo một lược đồ. Nếu trường đó bị thiếu, một ngoại lệ sẽ bị ném. Nhưng một trình biên dịch sẽ chỉ biết rằng đó là ném một ngoại lệ hoặc trả về một số loại JSONValue chung.

Một vi dụ khac:

x, y, z = struct.unpack("II6s", data)

"II6s" xác định cách dữ liệu mã hóa ba biến. Vì tôi đã chỉ định định dạng, tôi biết loại nào sẽ được trả về. Một trình biên dịch sẽ chỉ biết rằng nó trả về một tuple.

Chủ đề thống nhất của tất cả các ví dụ này là lập trình viên biết loại, nhưng một hệ thống loại mức Java sẽ không thể phản ánh điều đó. Trình biên dịch sẽ không biết các loại và do đó, một ngôn ngữ được gõ tĩnh sẽ không cho phép tôi gọi chúng, trong khi đó, một ngôn ngữ được gõ động sẽ.

Đó là những gì trích dẫn ban đầu nó nhận được tại:

Điều tuyệt vời về cách gõ động là nó cho phép bạn thể hiện bất cứ điều gì có thể tính toán được. Và các hệ thống loại không - các hệ thống loại thường có thể quyết định và chúng hạn chế bạn trong một tập hợp con.

Khi sử dụng kiểu gõ động, tôi có thể sử dụng loại dẫn xuất nhất mà tôi biết, không chỉ đơn giản là loại dẫn xuất nhất mà hệ thống loại ngôn ngữ của tôi biết. Trong tất cả các trường hợp trên, tôi có mã đúng về mặt ngữ nghĩa, nhưng sẽ bị hệ thống gõ tĩnh từ chối.

Tuy nhiên, để trở lại câu hỏi của bạn:

Tôi tự hỏi, có những mẫu hoặc chiến lược thiết kế hữu ích nào, bằng cách sử dụng công thức của trích dẫn, "không hoạt động như các loại"?

Bất kỳ ví dụ nào ở trên và thực sự là bất kỳ ví dụ nào về kiểu gõ động đều có thể được xác thực bằng cách gõ tĩnh bằng cách thêm các phôi phù hợp. Nếu bạn biết một loại trình biên dịch của bạn không, chỉ cần nói với trình biên dịch bằng cách truyền giá trị. Vì vậy, ở một mức độ nào đó, bạn sẽ không nhận được bất kỳ mẫu bổ sung nào bằng cách sử dụng kiểu gõ động. Bạn có thể cần phải truyền thêm để có được mã nhập tĩnh.

Ưu điểm của kiểu gõ động là bạn có thể chỉ cần sử dụng các mẫu này mà không phải lo lắng về thực tế rằng nó khó để thuyết phục hệ thống loại của bạn về tính hợp lệ của chúng. Nó không thay đổi các mẫu có sẵn, nó chỉ có thể giúp chúng dễ thực hiện hơn vì bạn không phải tìm ra cách làm cho hệ thống loại của bạn nhận ra mẫu hoặc thêm phôi để lật đổ hệ thống loại.


1
Tại sao java là điểm giới hạn mà bạn không nên truy cập vào 'hệ thống loại tiên tiến / phức tạp hơn'?
jk.

2
@jk, điều gì khiến bạn nghĩ đó là những gì tôi đang nói? Tôi rõ ràng tránh đứng về phía liệu một hệ thống loại tiên tiến / phức tạp hơn có đáng giá hay không.
Winston Ewert

2
Một số trong số này là những ví dụ khủng khiếp, và những cái khác dường như là quyết định ngôn ngữ nhiều hơn là gõ so với không gõ. Tôi đặc biệt bối rối tại sao mọi người nghĩ rằng khử lưu huỳnh rất phức tạp trong các ngôn ngữ đánh máy. Kết quả được gõ sẽ là data = parseJSON<SomeSchema>(someJson); print(data.properties.rowCount); và nếu một người không có một lớp để giải trừ để chúng ta có thể quay lại data = parseJSON(someJson); print(data["properties.rowCount"]);- cái vẫn được gõ và thể hiện cùng một ý định.
NPSF3000

2
@ NPSF3000, hàm parseJSON hoạt động như thế nào? Nó dường như hoặc sử dụng sự phản chiếu hoặc macro. Làm thế nào dữ liệu ["property.rowCount"] có thể được nhập bằng ngôn ngữ tĩnh? Làm thế nào nó có thể biết rằng giá trị kết quả là một số nguyên?
Winston Ewert

2
@ NPSF3000, bạn dự định sử dụng nó như thế nào nếu bạn không biết số nguyên của nó? Làm thế nào để bạn có kế hoạch lặp qua các phần tử trong một danh sách trong JSON mà không biết rằng đó là một mảng? Điểm của ví dụ của tôi là tôi biết đó data.propertieslà một đối tượng và tôi biết đó data.properties.rowCountlà một số nguyên và tôi chỉ có thể viết mã sử dụng chúng. Đề xuất của bạn data["properties.rowCount"]không cung cấp điều tương tự.
Winston Ewert

1

Dưới đây là một vài ví dụ từ Objective-C (được gõ động) không thể có trong C ++ (gõ tĩnh):

  • Đưa các đối tượng của một số lớp khác nhau vào cùng một thùng chứa.
    Tất nhiên, điều này yêu cầu kiểm tra kiểu thời gian chạy để sau đó diễn giải nội dung của vùng chứa và hầu hết bạn bè của kiểu gõ tĩnh sẽ phản đối rằng bạn không nên thực hiện việc này ngay từ đầu. Nhưng tôi đã thấy rằng, ngoài các cuộc tranh luận tôn giáo, điều này có thể có ích.

  • Mở rộng một lớp mà không cần phân lớp.
    Trong Objective-C, bạn có thể định nghĩa các hàm thành viên mới cho các lớp hiện có, bao gồm cả các lớp được xác định ngôn ngữ như NSString. Ví dụ: bạn có thể thêm một phương thức stripPrefixIfPresent:, để bạn có thể nói [@"foo/bar/baz" stripPrefixIfPresent:@"foo/"](lưu ý việc sử dụng các NSSringchữ @"").

  • Sử dụng các cuộc gọi lại hướng đối tượng.
    Trong các ngôn ngữ được nhập tĩnh như Java và C ++, bạn phải sử dụng độ dài đáng kể để cho phép thư viện gọi một thành viên tùy ý của đối tượng do người dùng cung cấp. Trong Java, cách giải quyết là cặp giao diện / bộ điều hợp cộng với một lớp ẩn danh, trong C ++, cách giải quyết thường là dựa trên mẫu, ngụ ý rằng mã thư viện phải được hiển thị với mã người dùng. Trong Objective-C, bạn chỉ cần chuyển tham chiếu đối tượng cộng với bộ chọn cho phương thức vào thư viện và thư viện có thể gọi và gọi lại trực tiếp.


Tôi có thể thực hiện lần đầu tiên trong C ++ bằng cách chuyển thành void *, nhưng đó là phá vỡ hệ thống loại, vì vậy nó không được tính. Tôi có thể thực hiện lần thứ hai trong C # bằng các phương thức mở rộng, hoàn hảo trong một hệ thống loại. Đối với phần ba, tôi nghĩ rằng "bộ chọn cho phương thức" có thể là lambda, vì vậy bất kỳ ngôn ngữ nào được gõ tĩnh với lambdas cũng có thể làm như vậy, nếu tôi hiểu chính xác. Tôi không quen thuộc với ObjC.
user7610

1
@JiriDanek "Tôi có thể thực hiện lần đầu tiên trong C ++ bằng cách chuyển thành void *", không chính xác, mã đọc các phần tử không có cách nào để tự lấy loại thực tế. Bạn cần thẻ loại. Ngoài ra, tôi không nghĩ rằng nói "Tôi có thể làm điều này bằng <ngôn ngữ>" là cách phù hợp / hiệu quả để xem xét điều này, bởi vì bạn luôn có thể mô phỏng chúng. Điều quan trọng là đạt được trong biểu hiện so với sự phức tạp của việc thực hiện. Ngoài ra, bạn dường như nghĩ rằng nếu một ngôn ngữ có cả khả năng tĩnh và động (Java, C #), thì ngôn ngữ đó chỉ thuộc về họ ngôn ngữ "tĩnh".
coredump

1
@JiriDanek void*một mình không phải là gõ năng động, nó là thiếu gõ. Nhưng vâng, Dynamic_cast, các bảng ảo, v.v. làm cho C ++ không hoàn toàn được nhập tĩnh. Nó có tệ không?
coredump

1
Nó gợi ý rằng có tùy chọn lật đổ hệ thống loại khi cần là hữu ích. Có một lối thoát khi bạn cần nó. Hoặc ai đó coi nó hữu ích. Nếu không họ sẽ không đưa nó vào ngôn ngữ.
user7610

2
@JiriDanek Tôi nghĩ rằng, bạn đã đóng đinh nó rất nhiều với bình luận cuối cùng của bạn. Những hầm thoát hiểm có thể cực kỳ hữu ích nếu được sử dụng cẩn thận. Tuy nhiên, với sức mạnh lớn đi kèm với trách nhiệm lớn, và rất nhiều người lạm dụng nó ... Vì vậy, sẽ tốt hơn rất nhiều khi sử dụng một con trỏ đến một lớp cơ sở chung mà tất cả các lớp khác đều có nguồn gốc từ định nghĩa (như trường hợp cả trong Objective-C và Java) và dựa vào RTTI để phân biệt các trường hợp, hơn là tạo một void*loại đối tượng cụ thể. Cái trước tạo ra lỗi thời gian chạy nếu bạn làm hỏng, kết quả sau đó trong hành vi không xác định.
cmaster
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.