Các nhà khai thác rõ ràng để đọc hơn từ khóa hoặc chức năng? [đóng cửa]


14

Đó là một chút chủ quan, nhưng tôi hy vọng sẽ hiểu rõ hơn về những yếu tố làm cho một nhà điều hành rõ ràng để sử dụng so với khó khăn và khó khăn. Gần đây tôi đã xem xét các thiết kế ngôn ngữ và một vấn đề mà tôi luôn luôn xoay quanh là khi nào thực hiện một số thao tác chính trong ngôn ngữ một toán tử và khi nào nên sử dụng từ khóa hoặc hàm.

Haskell có phần nổi tiếng về điều này, vì các toán tử tùy chỉnh dễ tạo và thường một kiểu dữ liệu mới sẽ được đóng gói với một số toán tử để sử dụng trên nó. Ví dụ, thư viện Parsec đi kèm với rất nhiều toán tử để kết hợp các trình phân tích cú pháp với nhau, với các loại đá quý như thế >..> tôi thậm chí không thể nhớ được ý nghĩa của chúng ngay bây giờ, nhưng tôi nhớ chúng rất dễ làm việc khi tôi đã ghi nhớ những gì họ thực sự có nghĩa. Một cuộc gọi chức năng như leftCompose(parser1, parser2)đã được tốt hơn? Chắc chắn dài dòng hơn, nhưng rõ ràng hơn trong một số cách.

Quá tải toán tử trong các ngôn ngữ giống như C là một vấn đề tương tự, nhưng bị giới hạn bởi vấn đề bổ sung là quá tải ý nghĩa của các toán tử quen thuộc như +với ý nghĩa mới khác thường.

Trong bất kỳ ngôn ngữ mới, điều này có vẻ như là một vấn đề khá khó khăn. Ví dụ, trong F #, việc truyền sử dụng toán tử đúc kiểu có nguồn gốc toán học, thay vì cú pháp truyền kiểu C # hoặc kiểu VB dài dòng. C #: (int32) xVB: CType(x, int32)F #:x :> int32

Về lý thuyết, một ngôn ngữ mới có thể có các toán tử cho hầu hết các chức năng tích hợp. Thay vì defhoặc dechoặc varkhai báo biến, tại sao không ! namehay @ namehoặc một cái gì đó tương tự. Nó chắc chắn rút ngắn khai báo theo sau ràng buộc: @x := 5thay vì declare x = 5hoặc let x = 5 Hầu hết mã sẽ yêu cầu rất nhiều định nghĩa biến, vậy tại sao không?

Khi nào thì một toán tử rõ ràng và hữu ích, và khi nào nó bị che khuất?



1
Tôi ước một trong số 1.000 nhà phát triển đã từng phàn nàn với tôi về việc thiếu quá tải toán tử của Java sẽ trả lời câu hỏi này.
smp7d

1
Tôi đã nghĩ về APL khi tôi gõ xong câu hỏi, nhưng trong một số cách làm rõ quan điểm và chuyển nó vào lãnh thổ có phần ít chủ quan hơn. Tôi nghĩ rằng hầu hết mọi người có thể đồng ý rằng APL mất một thứ gì đó dễ sử dụng thông qua số lượng nhà khai thác cực lớn của nó, nhưng lượng người phàn nàn khi một ngôn ngữ không có quá tải nhà điều hành chắc chắn nói về một số chức năng rất phù hợp với đóng vai trò là người vận hành Giữa các toán tử tùy chỉnh bị cấm và không có gì ngoài các toán tử, phải ẩn một số hướng dẫn để thực hiện và sử dụng thực tế.
CodexArcanum

Như tôi thấy, việc thiếu quá tải toán tử là không thể chấp nhận được vì nó đặc quyền các kiểu gốc hơn các kiểu do người dùng định nghĩa (lưu ý rằng Java cũng thực hiện điều này theo các cách khác). Tôi thích thú hơn nhiều về các toán tử tùy chỉnh theo kiểu Haskell, có vẻ như là một lời mời mở cho rắc rối ...
sắp diễn ra vào

2
@ Sắpstorm: Tôi thực sự nghĩ rằng cách Haskell là tốt hơn. Khi bạn có một tập hợp toán tử hữu hạn mà bạn có thể quá tải theo các cách khác nhau, bạn thường bị buộc phải sử dụng lại các toán tử trong các ngữ cảnh khác nhau (ví dụ: +nối chuỗi hoặc <<cho luồng). Mặt khác, với Haskell, một toán tử chỉ là một hàm có tên tùy chỉnh (nghĩa là không bị quá tải) hoặc là một phần của lớp loại, nghĩa là trong khi nó đa hình, nó thực hiện cùng một điều logic cho mọi loại và thậm chí có chữ ký cùng loại. Vì vậy, >>>>cho tất cả các loại và không bao giờ sẽ là một sự thay đổi chút.
Tikhon Jelvis

Câu trả lời:


16

từ quan điểm thiết kế ngôn ngữ chung, không cần có bất kỳ sự khác biệt nào giữa các hàm và toán tử. Người ta có thể mô tả các chức năng như các hoạt động tiền tố với bất kỳ (hoặc thậm chí biến). Và các từ khóa có thể được xem như chỉ là các hàm hoặc toán tử có tên dành riêng (rất hữu ích trong việc thiết kế một ngữ pháp).

Cuối cùng, tất cả những quyết định này đều thuộc về cách bạn muốn ký hiệu đọc và như bạn nói là chủ quan, mặc dù người ta có thể đưa ra một số hợp lý hóa rõ ràng, ví dụ như sử dụng các toán tử infix thông thường cho toán học như mọi người đều biết

Cuối cùng Dijkstra đã viết một lời biện minh thú vị về các ký hiệu toán học mà anh ta đã sử dụng, trong đó bao gồm một cuộc thảo luận tốt về sự đánh đổi của infix so với các ký hiệu tiền tố


4
Tôi muốn cung cấp cho bạn +1 thứ hai cho liên kết đến giấy Dijkstra.
AProgrammer

Liên kết tuyệt vời, bạn đã phải xuất bản một tài khoản PayPal! ;)
Adriano Repetti

8

Đối với tôi, một toán tử ngừng hữu ích khi bạn không còn có thể đọc to dòng mã thành tiếng hoặc trong đầu, và nó có ý nghĩa.

Ví dụ, declare x = 5đọc là "declare x equals 5"hoặc let x = 5có thể được đọc "let x equal 5"là rất dễ hiểu khi đọc thành tiếng, tuy nhiên đọc @x := 5được đọc là "at x colon equals 5"(hoặc "at x is defined to be 5"nếu bạn là nhà toán học), điều này hoàn toàn không có ý nghĩa.

Vì vậy, theo tôi, sử dụng một toán tử nếu mã có thể được đọc thành tiếng và được hiểu bởi một người thông minh hợp lý không quen thuộc với ngôn ngữ, nhưng sử dụng tên hàm nếu không.


5
Tôi không nghĩ @x := 5là khó để tìm ra - tôi vẫn đọc to cho chính mình nghe set x to be equal to 5.
Thất vọngWithFormsDesigner

3
@Rachel, trong trường hợp đó, :=có cách sử dụng rộng rãi hơn trong ký hiệu toán học bên ngoài các ngôn ngữ lập trình. Ngoài ra, các tuyên bố như x = x + 1trở thành một chút vô lý.
ccoakley

3
Trớ trêu thay, tôi đã quên rằng một số người sẽ không nhận ra đó :=là nhiệm vụ. Một vài ngôn ngữ thực sự sử dụng điều đó. Smalltalk, tôi nghĩ, có lẽ là người nổi tiếng nhất. Việc phân công và bình đẳng có nên là các toán tử riêng biệt hay không là một loại sâu hoàn toàn khác nhau, có lẽ người ta đã bao phủ bởi một câu hỏi khác.
CodexArcanum

1
@ccoakley Cảm ơn, tôi không biết rằng nó :=đã được sử dụng trong toán học. Một định nghĩa tôi tìm thấy cho nó là "được định nghĩa là" vì vậy @x := 5sẽ được dịch sang tiếng Anh "at x is defined to be 5", mà theo tôi vẫn không có ý nghĩa nhiều như nó cần.
Rachel

1
Chắc chắn Pascal đã sử dụng: = làm nhiệm vụ, vì thiếu mũi tên trái trong ASCII.
Vatine

4

Một toán tử rõ ràng và hữu ích khi nó quen thuộc. Và điều đó có lẽ có nghĩa là các toán tử quá tải chỉ nên được thực hiện khi toán tử được chọn gần đủ (như về hình thức, quyền ưu tiên và tính kết hợp) như một thực tiễn đã được thiết lập.

Nhưng đào sâu hơn một chút, có hai khía cạnh.

Cú pháp (infix cho toán tử trái ngược với tiền tố cho cú pháp gọi hàm) và đặt tên.

Đối với cú pháp, có một trường hợp khác trong đó infix đủ rõ ràng hơn tiền tố mà nó có thể đảm bảo nỗ lực trở nên quen thuộc: khi các cuộc gọi được kết nối và lồng vào nhau.

So sánh a * b + c * d + e * fvới +(+(*(a, b), *(c, d)), *(e, f))hoặc + + * a b * c d * e f(cả hai đều có thể phân tích cú pháp trong cùng điều kiện với phiên bản infix). Thực tế là các +thuật ngữ riêng biệt thay vì đi trước một trong số chúng theo một cách lâu dài làm cho nó dễ đọc hơn trong mắt tôi (nhưng bạn phải nhớ các quy tắc ưu tiên không cần thiết cho cú pháp tiền tố). Nếu bạn cần kết hợp mọi thứ theo cách này, cách lợi ích lâu dài đáng giá chi phí học tập. Và bạn giữ lợi thế này ngay cả khi bạn đặt tên cho các toán tử của mình bằng các chữ cái thay vì sử dụng các ký hiệu.

Liên quan đến việc đặt tên của các nhà khai thác, tôi thấy một số lợi thế trong việc sử dụng một cái gì đó ngoài các biểu tượng được thiết lập hoặc tên rõ ràng. Vâng, chúng có thể ngắn hơn, nhưng chúng thực sự khó hiểu và sẽ nhanh chóng bị lãng quên nếu bạn không có lý do chính đáng để biết chúng.

Khía cạnh thứ ba nếu bạn sử dụng POV thiết kế ngôn ngữ, là nếu tập hợp các toán tử nên mở hay đóng - bạn có thể thêm toán tử hay không - và nếu ưu tiên và kết hợp nên được người dùng xác định hay không. Việc đầu tiên của tôi là phải cẩn thận và không cung cấp mức độ ưu tiên và sự kết hợp có thể xác định của người dùng, trong khi tôi sẽ cởi mở hơn khi có một bộ mở (nó sẽ tăng lực đẩy để sử dụng quá tải toán tử, nhưng sẽ giảm một lần sử dụng một điều xấu chỉ để hưởng lợi từ cú pháp infix nơi nó hữu ích).


Nếu người dùng có thể thêm toán tử, tại sao không để họ đặt quyền ưu tiên và kết hợp của các toán tử mà họ đã thêm?
Tikhon Jelvis

@TikhonJelvis, Đó chủ yếu là tính bảo thủ, nhưng có một số khía cạnh cần xem xét nếu bạn muốn có thể sử dụng nó cho bất cứ điều gì khác ngoài các ví dụ. Chẳng hạn, bạn muốn liên kết mức độ ưu tiên và kết hợp với toán tử, không phải là thành viên nhất định của tập hợp quá tải (bạn đã chọn thành viên sau khi phân tích xong, lựa chọn không thể ảnh hưởng đến phân tích cú pháp). Do đó, để tích hợp các thư viện sử dụng cùng một toán tử, họ phải đồng ý về mức độ ưu tiên và tính kết hợp của nó. Trước khi thêm khả năng, tôi sẽ cố gắng tìm hiểu tại sao rất ít ngôn ngữ theo Algol 68 (không có AFAIK) và được phép định nghĩa chúng.
AProgrammer

Chà, Haskell cho phép bạn xác định các toán tử tùy ý và đặt mức độ ưu tiên và kết hợp của chúng. Sự khác biệt là bạn không thể quá tải toán tử trên mỗi se - chúng hoạt động như các hàm bình thường. Điều này có nghĩa là bạn không thể có các thư viện khác nhau cố gắng sử dụng cùng một toán tử cho những thứ khác nhau. Vì bạn có thể xác định toán tử của riêng mình, nên có ít lý do hơn để sử dụng lại cùng một toán tử cho những thứ khác nhau.
Tikhon Jelvis

1

Toán tử như là biểu tượng hữu ích khi chúng có ý nghĩa trực giác. +-rõ ràng là phép cộng và phép trừ, ví dụ, đó là lý do tại sao hầu hết mọi ngôn ngữ đều sử dụng chúng làm toán tử. Cùng với so sánh ( <>được dạy trong trường lớp, và <=>=là sự mở rộng trực quan của sự so sánh cơ bản khi bạn hiểu rằng không có phím gạch chân-so sánh trên bàn phím tiêu chuẩn.)

*/ít rõ ràng hơn ngay lập tức, nhưng chúng được sử dụng phổ biến và vị trí của chúng trên bàn phím số ngay bên cạnh +-các phím giúp cung cấp ngữ cảnh. C <<>>nằm trong cùng một danh mục: khi bạn hiểu dịch chuyển trái và dịch chuyển phải là gì, không quá khó để hiểu những câu thần chú đằng sau mũi tên.

Sau đó, bạn đến với một số trong những người thực sự kỳ lạ, những thứ có thể biến mã C thành súp toán tử không thể đọc được. (Chọn C ở đây vì đây là một ví dụ rất nặng về toán tử mà cú pháp của mọi người khá quen thuộc, thông qua làm việc với C hoặc với các ngôn ngữ con cháu.) Ví dụ: sau đó là %toán tử. Mọi người đều biết đó là gì: đó là dấu hiệu phần trăm ... phải không? Ngoại trừ trong C, nó không liên quan gì đến phép chia cho 100; đó là toán tử mô đun. Điều đó làm cho không có ý nghĩa thường xuyên, nhưng nó có!

Thậm chí tệ hơn là các toán tử boolean. &như một có ý nghĩa, ngoại trừ việc có hai &toán tử khác nhau làm hai việc khác nhau và cả hai đều có giá trị cú pháp trong mọi trường hợp một trong số chúng là hợp lệ, mặc dù chỉ một trong số chúng thực sự có ý nghĩa trong bất kỳ trường hợp cụ thể nào. Đó chỉ là một công thức cho sự nhầm lẫn! Các toán tử hay , xorkhông thậm chí còn tệ hơn, vì chúng không sử dụng các ký hiệu hữu ích về mặt lịch sử và cũng không nhất quán. (Không có đôi xor điều hành, và các logic và Bitwise không sử dụng hai biểu tượng khác nhau thay vì một biểu tượng và một phiên bản tăng gấp đôi-up.)

Và chỉ để làm cho nó tồi tệ hơn, các nhà khai thác &*được sử dụng lại cho những thứ hoàn toàn khác nhau, điều này làm cho ngôn ngữ khó hơn cho cả người và trình biên dịch để phân tích cú pháp. (Điều đó A * Bcó nghĩa là gì? Nó phụ thuộc hoàn toàn vào ngữ cảnh: là Amột loại hoặc một biến?)

Phải thừa nhận rằng, tôi chưa bao giờ nói chuyện với Dennis Ritchie và hỏi anh ấy về các quyết định thiết kế mà anh ấy đưa ra bằng ngôn ngữ, nhưng rất nhiều cảm giác như triết lý đằng sau các nhà khai thác là "biểu tượng vì lợi ích của biểu tượng".

Tương phản điều này với Pascal, có cùng toán tử với C, nhưng một triết lý khác trong việc thể hiện chúng: các toán tử phải trực quan và dễ đọc. Các toán tử số học là như nhau. Toán tử mô đun là từ mod, bởi vì không có ký hiệu trên bàn phím rõ ràng có nghĩa là "mô đun". Các toán tử logic là and, or, xornot, và có chỉ là một trong mỗi; trình biên dịch biết bạn cần phiên bản boolean hay bitwise dựa trên việc bạn đang vận hành trên booleans hay số, loại bỏ cả một lớp lỗi. Điều này làm cho mã Pascal xa dễ hiểu hơn so với mã C, đặc biệt là trong một trình soạn thảo hiện đại với cú pháp làm nổi bật để các nhà khai thác và từ khóa riêng biệt trực quan từ định danh.


4
Pascal hoàn toàn không đại diện cho một triết lý khác nhau. Nếu bạn đọc giấy tờ Wirth, ông sử dụng những biểu tượng cho những thứ như andortất cả các thời gian. Tuy nhiên, khi anh phát triển Pascal, anh đã làm nó trên máy tính lớn Control Data, sử dụng các ký tự 6 bit. Điều đó buộc phải quyết định sử dụng từ ngữ cho nhiều thứ, vì lý do đơn giản là bộ ký tự đơn giản là không nhiều không gian cho các biểu tượng và chẳng hạn như ASCII. Tôi đã sử dụng cả C và Pascal và thấy C dễ đọc hơn. Bạn có thể thích Pascal, nhưng đó không phải là vấn đề về hương vị chứ không phải thực tế.
Jerry Coffin

Để thêm vào nhận xét @JerryCoffin về Wirth, các ngôn ngữ tiếp theo của nó (Modula, Oberon) đang sử dụng nhiều biểu tượng hơn.
Lập trình viên

@AProgrammer: Và họ không bao giờ đi đâu cả. ;)
Mason Wheeler

Tôi nghĩ |(và, bằng cách mở rộng, ||) cho hoặc có ý nghĩa. Bạn cũng thấy nó luôn luôn thay thế trong các bối cảnh khác, như ngữ pháp, và có vẻ như nó đang chia hai tùy chọn.
Tikhon Jelvis

1
Một lợi thế của các từ khóa dựa trên chữ cái là không gian của chúng lớn hơn nhiều so với các ký hiệu. Ví dụ, ngôn ngữ kiểu Pascal có thể bao gồm các toán tử cho cả "mô đun" và "phần dư" và cho phép chia số nguyên so với dấu phẩy động (có ý nghĩa khác nhau ngoài các loại toán hạng của chúng).
supercat

1

Tôi nghĩ các nhà khai thác dễ đọc hơn nhiều khi chức năng của họ phản ánh chặt chẽ mục đích toán học quen thuộc của họ. Ví dụ: các toán tử tiêu chuẩn như +, -, v.v ... dễ đọc hơn Thêm hoặc Trừ khi thực hiện phép cộng và phép trừ. Hành vi của người vận hành phải được xác định rõ ràng. Tôi có thể chấp nhận sự quá tải về ý nghĩa của + cho việc ghép danh sách chẳng hạn. Lý tưởng nhất là hoạt động sẽ không có tác dụng phụ, trả về một giá trị thay vì đột biến.

Mặc dù vậy, tôi đã vật lộn với các toán tử phím tắt cho các chức năng như gấp. Rõ ràng nhiều kinh nghiệm với họ sẽ giảm bớt điều này, nhưng tôi thấy foldRightdễ đọc hơn /:.


4
Có lẽ tần suất sử dụng cũng đi vào nó. Tôi sẽ không nghĩ rằng bạn foldthường xuyên đủ để một nhà điều hành tiết kiệm rất nhiều thời gian. Đó cũng có thể là lý do tại sao LISPy (+ 1 2 3) có vẻ kỳ lạ nhưng (thêm 1 2 3) lại ít như vậy. Chúng tôi có xu hướng nghĩ về các nhà khai thác như là nhị phân nghiêm ngặt. Các >>.toán tử kiểu của Parsec có thể rất ngớ ngẩn, nhưng ít nhất điều chính bạn làm trong Parsec là kết hợp các trình phân tích cú pháp, vì vậy các toán tử cho nó được sử dụng rất nhiều.
CodexArcanum

1

Vấn đề với các toán tử là chỉ có một số ít trong số chúng so với số tên phương thức (!) Có thể được sử dụng thay thế. Một tác dụng phụ là các nhà khai thác có thể xác định người dùng có xu hướng giới thiệu rất nhiều quá tải.

Chắc chắn, dòng C = A * x + b * cdễ viết và đọc hơn C = A.multiplyVector(x).addVector(b.multiplyScalar(c)).

Hoặc, tốt, chắc chắn sẽ dễ viết hơn, nếu bạn có tất cả các phiên bản quá tải của tất cả các toán tử đó trong đầu. Và bạn có thể đọc nó sau đó, trong khi sửa lỗi thứ hai đến cuối cùng.

Bây giờ, đối với hầu hết các mã sẽ chạy ra khỏi đó, điều đó tốt. "Dự án vượt qua tất cả các thử nghiệm và nó chạy" - đối với nhiều phần mềm là tất cả những gì chúng ta có thể muốn.

Nhưng mọi thứ diễn ra khác đi khi bạn làm phần mềm đi vào các lĩnh vực quan trọng. An toàn thứ quan trọng. Bảo vệ. Phần mềm giữ máy bay trong không khí, hoặc giữ cho các cơ sở hạt nhân hoạt động. Hoặc phần mềm mã hóa email bí mật cao của bạn.

Đối với các chuyên gia bảo mật chuyên về kiểm toán mã, đây có thể là một cơn ác mộng. Sự pha trộn của sự phong phú của các toán tử do người dùng định nghĩa và quá tải toán tử có thể khiến họ rơi vào tình huống khó chịu khi họ gặp khó khăn trong việc tìm ra mã nào sẽ chạy cuối cùng.

Vì vậy, như đã nêu trong câu hỏi ban đầu, đây là một chủ đề rất chủ quan. Và trong khi nhiều gợi ý về cách sử dụng toán tử có thể nghe có vẻ hoàn toàn hợp lý, thì sự kết hợp chỉ một vài trong số chúng có thể tạo ra nhiều rắc rối trong thời gian dài.


0

Tôi cho rằng ví dụ cực đoan nhất là APL, chương trình sau đây là "trò chơi cuộc sống":

life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

Và nếu bạn có thể tìm ra tất cả những gì nó có nghĩa mà không mất một vài ngày với hướng dẫn tham khảo thì chúc bạn may mắn!

Vấn đề là bạn có một bộ các biểu tượng mà hầu hết mọi người đều hiểu được bằng trực giác: +,-,*,/,%,=,==,&

và phần còn lại yêu cầu một số giải thích trước khi chúng có thể được hiểu, và, nói chung là cụ thể cho ngôn ngữ cụ thể. Chẳng hạn, không có ký hiệu rõ ràng để chỉ định thành viên nào của mảng bạn muốn, [], () và thậm chí "." đã được sử dụng, nhưng cũng không có từ khóa rõ ràng nào bạn có thể dễ dàng sử dụng, do đó, có một trường hợp được đưa ra để sử dụng hợp lý các nhà khai thác. Nhưng không quá nhiều trong số họ.

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.