Tại sao kiểu đi sau tên biến trong các ngôn ngữ lập trình hiện đại?


57

Tại sao trong hầu hết các ngôn ngữ lập trình hiện đại (Go, Rust, Kotlin, Swift, Scala, Nim, thậm chí Python phiên bản cuối cùng) luôn xuất hiện sau tên biến trong khai báo biến chứ không phải trước?

Tại sao x: int = 42và không int x = 42?
Là cái sau không dễ đọc hơn cái trước?
Nó chỉ là một xu hướng hoặc có bất kỳ lý do thực sự có ý nghĩa đằng sau giải pháp này?



3
Đúng, đó là một bản dupe ổn. Mặc dù nó đề cập cụ thể đến ngôn ngữ Kotlin, câu hỏi (và câu trả lời của nó) áp dụng cho tất cả các ngôn ngữ có thực hành này.
Robert Harvey


2
Xin lỗi các bạn, tôi đã thấy câu hỏi về Kotlin khi googling, nhưng tôi đã hỏi lại nó để không nhận được câu trả lời như "Bởi vì nhóm phát triển của Kotlin (Go, Rust, ...) đã quyết định như thế này". Tôi đã quan tâm đến một câu trả lời phổ biến hơn: tại sao nó trở thành một loại dịch?
Andre Polykanine

2
Hihi, vì vậy Delphi, là (Đối tượng) Pascal, và đã có các loại sau tên biến kể từ khi thành lập, trở lại khi ở thời kỳ đen tối của thế kỷ trước, lại hiện đại;) Btw, khả năng đọc bị ảnh hưởng nhiều bởi những gì bạn đang có đã từng. Đến từ Delphi, C # với loại trước tên var, khiến tôi phải đập đầu mình khá lâu.
Marjan Venema

Câu trả lời:


64

Tất cả các ngôn ngữ bạn đề cập đến suy luận loại hỗ trợ , có nghĩa là loại đó là một phần tùy chọn của khai báo trong các ngôn ngữ đó vì chúng đủ thông minh để tự điền nó khi bạn cung cấp một biểu thức khởi tạo có loại dễ xác định.

Điều đó quan trọng bởi vì đặt các phần tùy chọn của biểu thức xa hơn bên phải sẽ giảm phân tích sự mơ hồ và tăng tính nhất quán giữa các biểu thức sử dụng phần đó và phần không biểu thức. Việc phân tích một khai báo chỉ đơn giản hơn khi bạn biết vartừ khóa và tên biến là bắt buộc trước khi bạn có được các công cụ tùy chọn. Về lý thuyết, tất cả những điều giúp phân tích cú pháp cho máy tính dễ dàng hơn cũng sẽ cải thiện khả năng đọc tổng thể cho con người, nhưng điều đó còn gây tranh cãi hơn nhiều.

Lập luận này được đặc biệt mạnh mẽ khi bạn xem xét tất cả các tùy chọn bổ kiểu đó một ngôn ngữ "không hiện đại" như C ++ có, chẳng hạn như *đối với con trỏ, &tài liệu tham khảo, const, volatilevà vân vân. Khi bạn ném vào dấu phẩy cho nhiều khai báo, bạn bắt đầu nhận được một số sự mơ hồ thực sự kỳ lạ như int* a, b;không tạo ra bmột con trỏ.

Ngay cả C ++ hiện cũng hỗ trợ khai báo "gõ bên phải" ở dạng auto x = int{ 4 };nó có một số lợi thế .


2
+1 để thừa nhận sự tồn tại của các từ khóa bắt buộc như thế varcung cấp phần lớn việc đơn giản hóa phân tích cú pháp.
8bittree

2
Không phải sự tồn tại của vartừ khóa đã đánh bại mục đích của ký hiệu tên đầu tiên? Tôi không thấy lợi thế của việc viết var userInput: String = getUserInput()hơn String userInput = getUserInput(). Lợi thế sẽ chỉ có liên quan nếu userInput = getUserInput()cũng được cho phép, nhưng điều đó có nghĩa là khai báo ngầm định của một biến có phạm vi, mà tôi thấy khá gớm.
kdb

2
@kdb trong các ngôn ngữ như C # và C ++ sẽ là var userInput = getUserInput()hoặc auto userInput = getUserInput(), trình biên dịch biết getUserInput()trả về Stringnên bạn không phải chỉ định nó với từ khóa khấu trừ loại. userInput = getUserInput()Tất nhiên là sử dụng trước khi tuyên bố.
Wes Toleman

32

Tại sao trong gần như tất cả các ngôn ngữ lập trình hiện đại (Go, Rust, Kotlin, Swift, Scala, Nim, thậm chí phiên bản Python cuối cùng) luôn xuất hiện sau khai báo biến, chứ không phải trước?

Tiền đề của bạn là thiếu sót trên hai mặt trận:

  • Có những ngôn ngữ lập trình mới có loại trước định danh. C, D, hoặc Ceylon chẳng hạn.
  • Có loại sau khi định danh không phải là một hiện tượng mới, nó quay trở lại ít nhất là Pascal (được thiết kế năm 196819191969, phát hành năm 1970), nhưng thực sự đã được sử dụng trong lý thuyết loại toán học, bắt đầu ~ 1902. Nó cũng được sử dụng trong ML (1973), CLU (1974), Hope (1970), Modula-2 (1977 Hóa1985), Ada (1980), Miranda (1985), Caml (1985), Eiffel (1985), Oberon (1986), Modula-3 (1986 Từ1988) và Haskell (1989).

Pascal, ML, CLU, Modula-2 và Miranda đều là những ngôn ngữ có ảnh hưởng rất lớn, vì vậy không có gì đáng ngạc nhiên khi phong cách khai báo kiểu này vẫn phổ biến.

Tại sao x: int = 42và không int x = 42? Là cái sau không dễ đọc hơn cái trước?

Khả năng đọc là một vấn đề quen thuộc. Cá nhân, tôi thấy tiếng Trung là không thể đọc được, nhưng rõ ràng, người Trung Quốc thì không. Học Pascal ở trường, học hỏi về Eiffel, F♯, Haskell và Scala, và đã xem TypeScript, Fortress, Go, Rust, Kotlin, Idris, Frege, Agda, ML, Ocaml, Nott, nó trông hoàn toàn tự nhiên đối với tôi .

Nó chỉ là một xu hướng hoặc có bất kỳ lý do thực sự có ý nghĩa đằng sau một giải pháp như vậy?

Nếu đó là một xu hướng, nó khá dai dẳng: như tôi đã đề cập, nó đã tồn tại hàng trăm năm trong toán học.

Một trong những lợi thế chính của việc có loại sau mã định danh, là dễ dàng loại bỏ loại này nếu bạn muốn nó được suy ra. Nếu khai báo của bạn trông như thế này:

val i: Int = 10

Sau đó, thật là tầm thường khi loại bỏ các loại và suy ra nó như thế này:

val i = 10

Trong khi đó nếu loại đi trước định danh như thế này:

Int i = 10

Sau đó, nó bắt đầu khó phân tích cú pháp để phân biệt một biểu thức với một khai báo:

i = 10

Giải pháp mà các nhà thiết kế ngôn ngữ thường đưa ra là giới thiệu từ khóa "Tôi không muốn viết một loại" phải viết thay vì loại:

var  i = 10; // C♯
auto i = 10; // C++

Nhưng điều này thực sự không có nhiều ý nghĩa: về cơ bản bạn phải viết một loại rõ ràng rằng bạn không viết một loại. Huh? Sẽ dễ dàng và hợp lý hơn nhiều nếu chỉ bỏ nó đi, nhưng điều đó làm cho ngữ pháp phức tạp hơn nhiều.

(Và chúng ta thậm chí không nói về các loại con trỏ hàm trong C.)

Các nhà thiết kế của một số ngôn ngữ nói trên đã cân nhắc về chủ đề này:

  • Câu hỏi thường gặp (xem thêm: Cú pháp khai báo của Go ):

    Tại sao khai báo ngược?

    Chúng chỉ ngược nếu bạn đã quen với C. Trong C, khái niệm là một biến được khai báo giống như một biểu thức biểu thị loại của nó, đó là một ý tưởng hay, nhưng ngữ pháp loại và biểu thức không trộn lẫn rất tốt và kết quả có thể gây nhầm lẫn; xem xét con trỏ hàm. Đi phần lớn tách biệt cú pháp biểu thức và kiểu và đơn giản hóa mọi thứ (sử dụng tiền tố *cho con trỏ là một ngoại lệ chứng minh quy tắc). Trong C, khai báo

    int* a, b;
    

    tuyên bố alà một con trỏ nhưng không b; trong đi

    var a, b *int
    

    tuyên bố cả hai là con trỏ Điều này là rõ ràng và thường xuyên hơn. Ngoài ra, :=biểu mẫu khai báo ngắn lập luận rằng một khai báo biến đầy đủ sẽ trình bày cùng thứ tự như :=vậy

    var a uint64 = 1
    

    có tác dụng tương tự như

    a := uint64(1)
    

    Phân tích cú pháp cũng được đơn giản hóa bằng cách có một ngữ pháp riêng biệt cho các loại không chỉ là ngữ pháp biểu thức; từ khóa như funcchangiữ cho mọi thứ rõ ràng.

  • Câu hỏi thường gặp về Kotlin :

    Tại sao có khai báo kiểu bên phải?

    Chúng tôi tin rằng nó làm cho mã dễ đọc hơn. Bên cạnh đó, nó cho phép một số tính năng cú pháp tốt đẹp. Ví dụ, thật dễ dàng để bỏ các chú thích loại. Scala cũng đã chứng minh khá tốt đây không phải là vấn đề.

  • Lập trình trong Scala :

    Độ lệch lớn so với Java liên quan đến cú pháp cho các chú thích kiểu Đây là " variable: Type" thay vì " Type variable" trong Java. Cú pháp loại hậu tố của Scala giống với Pascal, Modula-2 hoặc Eiffel. Lý do chính cho sự sai lệch này liên quan đến suy luận kiểu, điều này thường cho phép bạn bỏ qua loại biến hoặc kiểu trả về của phương thức. Sử dụng variable: Typecú pháp "", điều này thật dễ dàng, chỉ cần bỏ dấu hai chấm và kiểu. Nhưng trong Type variablecú pháp " " kiểu C ", bạn không thể bỏ qua kiểu kiểu Haiti, sẽ không có điểm đánh dấu để bắt đầu định nghĩa nữa. Bạn cần một số từ khóa thay thế để trở thành một trình giữ chỗ cho một loại bị thiếu (C♯ 3.0, một số loại suy luận, sử dụng varcho mục đích này). Một từ khóa thay thế như vậy cảm thấy đặc biệt hơn và ít thường xuyên hơn so với cách tiếp cận của Scala.

Lưu ý: các nhà thiết kế của Ceylon cũng ghi lại lý do tại sao họ sử dụng cú pháp loại tiền tố :

Tiền tố thay vì chú thích loại hậu tố

Tại sao bạn theo dõi C và Java trong việc đặt các chú thích kiểu trước, thay vì Pascal và ML đặt chúng sau tên khai báo?

Bởi vì chúng tôi nghĩ rằng:

shared Float e = ....
shared Float log(Float b, Float x) { ... }

Đơn giản là dễ đọc hơn nhiều so với điều này:

shared value e: Float = .... 
shared function log(b: Float, x: Float): Float { ... }

Và chúng tôi chỉ đơn giản là không hiểu làm thế nào bất cứ ai có thể nghĩ khác!

Cá nhân, tôi thấy "lập luận" của họ kém thuyết phục hơn những người khác.


4
Dường như khả năng bỏ qua các loại và vẫn có phân tích cú pháp đơn giản là do việc bổ sung var, auto, let, hoặc tương tự, không phải do phía nào của biến khai báo kiểu trên. var Int x = 5hoặc thậm chí Int var x = 5cả hai có thể được rút ngắn var x = 5.
8bittree

5
Mặc dù tôi thích nó có thể đọc được như nhau một khi bạn đã quen với nó và đối số loại tùy chọn rất mạnh, tôi muốn chỉ ra rằng IDE không thể tự động đề xuất tên biến dựa trên loại. Tôi nhớ điều đó khi tôi viết TypeScript.
Pierre Henry

"Your premise is flawed on two fronts" Tất cả những thứ đó đều mới hơn C # hoặc D.
Det

3
"Nhưng điều này thực sự không có nhiều ý nghĩa: về cơ bản bạn phải viết rõ ràng một loại nói rằng bạn không viết một loại." Chà, phiên bản kiểu bên phải hoàn toàn giống trong ví dụ của bạn ... Ngoài ra tôi không đồng ý rằng nó không có ý nghĩa gì. Nó có ý nghĩa chính xác như functrong Go.
Sốt

4

Nhân tiện, Pascal cũng làm điều đó và không phải là một ngôn ngữ mới. Đó là một ngôn ngữ hàn lâm, được thiết kế từ đầu.

Tôi muốn nói rằng nó rõ ràng hơn về mặt ngữ nghĩa để bắt đầu với tên biến. Loại chỉ là một chi tiết kỹ thuật. Nếu bạn muốn đọc lớp của bạn giống như mô hình của thực tế, thì nên đặt tên của các thực thể của bạn trước và triển khai kỹ thuật của chúng sau cùng.

C # và java xuất phát từ C nên họ phải tôn trọng "nghệ thuật trước", để không nhầm lẫn với lập trình viên có kinh nghiệm.


3
Ada cũng làm theo cách đó, nhưng Ada chắc chắn không phải là một "ngôn ngữ học thuật".
John R. Strohm

Ada đã làm điều đó bởi vì nó đã tạo ra rất nhiều từ Pascal và Modula 2.
Gort the Robot

2
@StevenBurnap, một tìm kiếm nhanh cho thấy cuốn sách đầu tiên của Wirth về Modula-2 ra mắt vào giữa năm 1982. Ada đã được phát triển vài năm trước đó (DoD1 là vào năm 1977 hoặc lâu hơn, như tôi nhớ lại), và được tiêu chuẩn hóa, theo cả tiêu chuẩn ANSI và MIL-STD, vào năm 1983. Cả bốn đội đã gửi ứng cử viên cho Ada đều tham gia PASCAL là điểm khởi đầu của họ. (Bell Labs đã được DoD mời gửi một ngôn ngữ ứng cử viên dựa trên C. Họ đã từ chối, nói rằng C không phải là như vậy và sẽ không bao giờ đủ mạnh cho phần mềm quan trọng của nhiệm vụ DoD.)
John R. Strohm

Tôi phải sai lầm. Tôi nghĩ Wirth đã tham gia vào Ada.
Gort Robot

Pascal bắt đầu sử dụng ngôn ngữ hàn lâm, nhưng đến cuối thập niên 90, nó đã trở thành ngôn ngữ để viết các ứng dụng Windows thông qua Delphi, đó là cho đến khi Borland nhảy lên cá mập với giá cả và bỏ ngỏ cho Java và C # đến và ăn cắp giải thưởng. Bạn vẫn sẽ thấy một tấn Delphi ngoài kia trong thế giới ứng dụng windows cũ.
Shayne

1

Tại sao x: int = 42và không int x = 42? Là cái sau không dễ đọc hơn cái trước?

Trong một ví dụ đơn giản như vậy, không có nhiều khác biệt, nhưng hãy làm cho nó phức tạp hơn một chút: int* a, b;

Đây là một tuyên bố hợp lệ thực tế trong C, nhưng nó không làm những gì nó trực quan trông giống như nó nên làm. Có vẻ như chúng ta đang khai báo hai biến loại int*, nhưng thực ra chúng ta đang khai báo một int*và một biến int.

Nếu ngôn ngữ của bạn đặt loại sau tên biến (s) trong khai báo của nó, vấn đề đó là không thể chạy vào.


4
Tôi không chắc tại sao di chuyển khai báo kiểu sau sẽ ảnh hưởng đến điều này. Là x, y *int;hai *inthay một *intvà một int? Tạo int*hoặc *intmột mã thông báo thay vì hai sẽ giải quyết nó hoặc sử dụng một yếu tố cú pháp bổ sung như :, có thể hoạt động theo một trong hai hướng.
8bittree

@ 8bittree Mỗi ngôn ngữ tôi quen thuộc sử dụng khai báo tên đầu tiên sử dụng mã thông báo bổ sung, như :hoặc as, giữa tên và loại.
Mason Wheeler


2
Và trong Go, x, y *intcó nghĩa là "khai báo hai biến, x và y, với kiểu con trỏ tới int".
Vatine

10
C # sử dụng cú pháp tiền tố, nhưng nó xử lý int[] x, ynhư hai mảng. Đó chỉ là cú pháp C ngớ ngẩn, coi các biến tố đó là các toán tử đơn nguyên trên biến (và sự pha trộn giữa tiền tố và hậu tố, không kém) gây ra vấn đề.
CodeInChaos
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.