Giải thích tốt về Nguyên tắc Tương ứng của Tennent là gì?


21

Tôi thấy mình phải vật lộn để xem nguyên tắc này là gì và tại sao nó lại quan trọng đối với thiết kế ngôn ngữ.

Về cơ bản, nó tuyên bố rằng, đối với mọi biểu thức exprtrong ngôn ngữ nên giống hệt như cấu trúc này:

(function () { return expr; })()

Ngoài ra, tôi đã nghe nói rằng Ruby tuân thủ nguyên tắc này, trong khi Python thì không. Tôi không hiểu tại sao điều này là đúng, hoặc nếu nó đúng.


3
Tôi không thể hiểu tại sao nó lạc đề, ai đó có thể giải thích cho tôi không?
Andrew

3
Có một vài vấn đề; Tôi đã chạm vào nó và đang gửi nó cho các lập trình viên, nơi các cuộc thảo luận như thế này được chào đón hơn một chút.
Ripped

1
Ruby không tuân theo nguyên tắc này: Giả sử exprcó dấu vết ngăn xếp hiện tại.
Landei

Câu trả lời:


18

Tôi chưa bao giờ nghe nói về "Nguyên tắc tương ứng của Tennent" và thậm chí ít quan trọng hơn trong thiết kế ngôn ngữ. Googling các biểu thức dường như tất cả dẫn đến một blog Neal Gafter năm 2006, nó đưa ra những gì anh ấy nghĩ và đó là cách anh ấy nghĩ rằng nó cũng nên áp dụng cho việc đóng cửa. Và hầu hết tất cả những người khác trong các diễn đàn dường như đề cập đến mục của Gafter.

Tuy nhiên, đây là một đề cập đến "TCP" đã nói của Douglas Crockford (một cái tên tôi biết và tin tưởng): http://java.sys-con.com/node/793338/ . Một phần

Có một số điều không thể được bao gồm theo cách đó, chẳng hạn như tuyên bố trả lại và tuyên bố phá vỡ, mà những người ủng hộ tuyên bố Nguyên tắc tương ứng (hoặc TCP) của Tennent là một triệu chứng của mùi hôi. Yow! Thiết kế ngôn ngữ đã đủ khó khăn mà không phải đối phó với ảo giác khứu giác. Vì vậy, để hiểu rõ hơn về vấn đề này, tôi đã mua một bản sao của cuốn sách 1981 của Tennent, Nguyên tắc ngôn ngữ lập trình.

Nó chỉ ra rằng Nguyên tắc tương ứng là mô tả , không quy định . Ông sử dụng nó để phân tích ngôn ngữ lập trình Pascal (hiện đã bị lãng quên), cho thấy sự tương ứng giữa các định nghĩa biến và các tham số thủ tục. Tennent không xác định sự thiếu tương ứng của các báo cáo hoàn trả là một vấn đề .

Do đó, dường như cái tên "Nguyên tắc tương ứng của Tennent" được sử dụng sai và bất cứ điều gì Neal nói về có thể nên được gọi là "TCP tưởng tượng và có thể khái quát hóa của Gafter" ... hoặc một số như vậy. Trong mọi trường hợp không đủ để ẩn đằng sau một bức màn tên cuốn sách không còn xuất bản


1
+1 cho "TCP tưởng tượng và có thể khái quát
hóa của Gafter

9

Tôi thấy đây là một phần của một quy tắc chung rằng một ngôn ngữ được thiết kế tốt sẽ làm những gì mà một lập trình viên mong đợi một cách tự nhiên. Nếu tôi có một khối mã mà tôi muốn cấu trúc lại thành một bao đóng và tôi bọc khối đó bằng cú pháp chiếm dụng mà không thực sự nghĩ về các dòng mã riêng lẻ, thì tôi hy vọng khối đó sẽ làm điều tương tự trong bao đóng đã làm nội tuyến. Nếu một số câu lệnh sử dụng từ khóa "this" (có lẽ là ngầm) và ngôn ngữ làm cho "this" được sử dụng bên trong bao đóng tham chiếu đến một lớp ẩn danh được sử dụng để đại diện cho nó chứ không phải là lớp xác định phương thức xác định bao đóng, thì ý nghĩa của những tuyên bố đó đã thay đổi, khối mã của tôi không còn làm những gì tôi nghĩ nữa và tôi phải theo dõi một lỗi và tìm ra cách thay đổi mã của mình để hoạt động trong bao đóng.

Vấn đề cũng có thể được giảm thiểu bằng IDE với các công cụ tái cấu trúc thông minh, có thể trích xuất các bao đóng, phát hiện các vấn đề tiềm ẩn và thậm chí tự động điều chỉnh mã được trích xuất để giải quyết các vấn đề.


3

Claus Rebke: liên quan đến "Thiết kế ngôn ngữ dựa trên các nguyên tắc ngữ nghĩa" của Tennent
Đưa ra cách giải thích thú vị về các nguyên tắc:
"Tương ứng là nguyên tắc cho phép chúng ta nói rằng

let(this=obj, x=5) { .. }  

((function(x) { .. }).call(obj,5))  

phải tương đương và rằng bất cứ điều gì chúng ta có thể làm trong danh sách tham số chính thức, chúng ta cũng có thể thực hiện trong khai báo và ngược lại. "[xem thêm, Rebke, bên dưới.]

RD Tennent: Các phương pháp thiết kế ngôn ngữ dựa trên các nguyên tắc ngữ nghĩa
"Hai phương pháp thiết kế ngôn ngữ dựa trên các nguyên tắc xuất phát từ cách tiếp cận biểu thị cho ngữ nghĩa ngôn ngữ lập trình được mô tả và minh họa bằng một ứng dụng cho ngôn ngữ Pascal. Trước tiên, các nguyên tắc này là sự tương ứng giữa tham số và Các cơ chế khai báo, và thứ hai, một nguyên tắc trừu tượng hóa cho các ngôn ngữ lập trình được điều chỉnh từ lý thuyết tập hợp. Một số phần mở rộng và khái quát hóa hữu ích của Pascal xuất hiện bằng cách áp dụng các nguyên tắc này, bao gồm giải pháp cho vấn đề tham số mảng và phương tiện mô đun hóa. "

Claus Rebke: "Về lập trình chức năng, thiết kế ngôn ngữ và sự bền bỉ" trên Haskell


Claus Rebke: "Về lập trình chức năng, thiết kế ngôn ngữ và sự bền bỉ" trên Haskell tại Community.haskell.org/~claus/publications/fpldp.html
Kris

2

Để trả lời câu hỏi tại sao CP của Tennent lại quan trọng đối với thiết kế ngôn ngữ, tôi muốn trích dẫn Neal Gafter :

Các nguyên tắc của Tennent rất mạnh mẽ vì các vi phạm của chúng có xu hướng hiển thị trong ngôn ngữ là sai sót, bất thường, hạn chế không cần thiết, tương tác hoặc biến chứng bất ngờ, v.v.

Bất kỳ hành vi vi phạm TCP nào cũng có khả năng làm tổn thương một số lập trình viên trong tương lai khi anh ta mong đợi các bao đóng hoạt động giống như mã không đóng nhưng thấy rằng, vi phạm TCP, họ không làm như vậy.


1

RE Python không theo nguyên tắc này. Nói chung, nó không theo nguyên tắc. Ví dụ cơ bản:

>>> x = ['foo']
>>> x
['foo']
>>> x = (lambda: ['foo'])()
>>> x
['foo']

Tuy nhiên, Python định nghĩa các biểu thứccâu lệnh riêng biệt. Vì ifcác nhánh, whilevòng lặp, gán hủy và các câu lệnh khác hoàn toàn không thể được sử dụng trong các lambdabiểu thức, nên chữ cái của nguyên tắc Tennent không áp dụng cho chúng. Mặc dù vậy, việc hạn chế bản thân chỉ sử dụng các biểu thức Python vẫn tạo ra một hệ thống hoàn chỉnh Turing. Vì vậy, tôi không thấy điều này là vi phạm nguyên tắc; hay đúng hơn, nếu nó vi phạm nguyên tắc, thì không có ngôn ngữ nào định nghĩa các câu và biểu thức riêng biệt có thể phù hợp với nguyên tắc này.

Ngoài ra, nếu phần thân của lambdabiểu thức đang ghi lại dấu vết ngăn xếp hoặc thực hiện phần hướng nội khác trong VM, điều đó có thể gây ra sự khác biệt. Nhưng theo tôi thì điều này không nên được coi là vi phạm. Nếu expr(lambda: expr)() nhất thiết phải biên dịch theo cùng mã byte, thì nguyên tắc thực sự liên quan đến trình biên dịch không phải là ngữ nghĩa; nhưng nếu chúng có thể biên dịch thành mã byte khác nhau, chúng ta không nên mong đợi trạng thái VM giống hệt nhau trong từng trường hợp.

Một bất ngờ có thể gặp phải khi sử dụng cú pháp hiểu, mặc dù tôi tin rằng điều này cũng không vi phạm nguyên tắc Tennent. Thí dụ:

>>> [x for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [lambda: x for x in xrange(10)]]  # surprise!
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
>>> # application of Tennent principle to first expression
... [(lambda: x)() for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [(lambda x: lambda: x)(x) for x in xrange(10)]]  # force-rebind x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> map(lambda f:f(), map(lambda x: lambda: x, xrange(10)))  # no issue with this form
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Điều ngạc nhiên là kết quả của cách hiểu danh sách được xác định. Cách hiểu "bất ngờ" ở trên tương đương với mã này:

>>> result = []
>>> for x in xrange(10):
...   # the same, mutable, variable x is used each time
...   result.append(lambda: x)
... 
>>> r2 = []
>>> for f in result:
...   r2.append(f())
... 
>>> r2
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

Nhìn theo cách này, sự hiểu biết 'bất ngờ' ở trên ít gây ngạc nhiên hơn và không vi phạm nguyên tắc Tennent.

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.