Tại sao hầu hết các ngôn ngữ lập trình có từ khóa hoặc cú pháp đặc biệt để khai báo các hàm? [đóng cửa]


39

Hầu hết các ngôn ngữ lập trình (cả ngôn ngữ được gõ động và tĩnh) đều có từ khóa và / hoặc cú pháp đặc biệt trông khác nhiều so với khai báo biến để khai báo hàm. Tôi thấy các chức năng giống như khai báo một thực thể có tên khác:

Ví dụ trong Python:

x = 2
y = addOne(x)
def addOne(number): 
  return number + 1

Tại sao không:

x = 2
y = addOne(x)
addOne = (number) => 
  return number + 1

Tương tự, trong một ngôn ngữ như Java:

int x = 2;
int y = addOne(x);

int addOne(int x) {
  return x + 1;
}

Tại sao không:

int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
  return x + 1;
}

Cú pháp này có vẻ tự nhiên hơn khi khai báo một cái gì đó (có thể là một hàm hoặc một biến) và một từ khóa ít hơn như defhoặc functiontrong một số ngôn ngữ. Và, IMO, nó phù hợp hơn (tôi nhìn vào cùng một vị trí để hiểu loại biến hoặc hàm) và có thể làm cho trình phân tích cú pháp / ngữ pháp đơn giản hơn một chút để viết.

Tôi biết rất ít ngôn ngữ sử dụng ý tưởng này (CoffeeScript, Haskell) nhưng hầu hết các ngôn ngữ phổ biến đều có cú pháp đặc biệt cho các hàm (Java, C ++, Python, JavaScript, C #, PHP, Ruby).

Ngay cả trong Scala, hỗ trợ cả hai cách (và có kiểu suy luận), việc viết phổ biến hơn:

def addOne(x: Int) = x + 1

Thay vì:

val addOne = (x: Int) => x + 1

IMO, ít nhất trong Scala, đây có lẽ là phiên bản dễ hiểu nhất nhưng thành ngữ này hiếm khi được theo sau:

val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1

Tôi đang làm việc với ngôn ngữ đồ chơi của riêng mình và tôi tự hỏi liệu có bất kỳ cạm bẫy nào không nếu tôi thiết kế ngôn ngữ của mình theo cách như vậy và nếu có bất kỳ lý do lịch sử hoặc kỹ thuật nào thì mô hình này không được theo dõi rộng rãi?


9
Lý do có khả năng lịch sử. Bất cứ ai đã làm nó đầu tiên làm theo cách đó, và mọi người khác sao chép. Nhưng tôi nghi ngờ chúng ta có thể biết chắc chắn.

29
Tôi nghĩ rằng đó là bởi vì một hàm hoặc phương thức đơn giản không chỉ là một thực thể có tên khác . Trong các ngôn ngữ chức năng, chúng là (bạn có thể chuyển chúng xung quanh, v.v.) nhưng trong các ngôn ngữ khác (như Java), một hàm hoặc phương thức hoàn toàn khác với một biến đơn giản và không thể được xử lý như vậy (Tôi thừa nhận loại Java 8 làm suy yếu điều này câu lệnh), vì vậy sẽ hợp lý khi định nghĩa các hàm / phương thức khác nhau, vì chúng hành xử khác nhau.
11684

15
Tôi không đồng ý rằng đề xuất của bạn là một cách khai báo hàm tự nhiên hơn. Tôi thực sự không thích cú pháp của Coffeescript và một phần trong đó là liên quan đến cú pháp khai báo hàm của nó. Thực tế là, nếu nó không bị hỏng thì đừng sửa nó. Viết 'def' hoặc 'function' không phải là vấn đề lớn và rõ ràng là một cái nhìn thoáng qua so với một số biểu tượng giống như Perl lạ mắt mà với đôi mắt không suy nghĩ hoặc mệt mỏi có thể dễ dàng bị nhầm lẫn với thứ khác. Trên một ghi chú hoàn toàn cá nhân tôi cũng nghĩ rằng cú pháp được đề xuất của bạn trong các ví dụ trông xấu hơn nhiều so với cú pháp hiện tại.
Roy

22
Làm thế nào là '=>' không phải là "từ khóa hoặc cú pháp đặc biệt để khai báo hàm"?
TML

11
Tôi không biết về bạn, nhưng với tôi (int => int) addOne = (x) => {thì "đặc biệt" và "phức tạp" hơn nhiều so với int addOne(int) {...
Bogdan Alexandru

Câu trả lời:


48

Tôi nghĩ lý do là hầu hết các ngôn ngữ phổ biến đều đến từ hoặc bị ảnh hưởng bởi họ ngôn ngữ C trái ngược với ngôn ngữ chức năng và gốc của chúng, tính toán lambda.

Và trong các ngôn ngữ này, các hàm không chỉ là một giá trị khác:

  • Trong C ++, C # và Java, bạn có thể quá tải các hàm: bạn có thể có hai hàm có cùng tên, nhưng chữ ký khác nhau.
  • Trong C, C ++, C # và Java, bạn có thể có các giá trị đại diện cho các hàm, nhưng con trỏ hàm, hàm functor, đại biểu và giao diện chức năng đều khác biệt với chính các hàm. Một phần lý do là hầu hết những thứ đó không thực sự chỉ là các chức năng, chúng là một chức năng cùng với một số trạng thái (có thể thay đổi).
  • Biến là có thể thay đổi theo mặc định (bạn phải sử dụng const, readonlyhoặc finalcấm đột biến), nhưng chức năng không thể được bố trí.
  • Từ góc độ kỹ thuật hơn, mã (bao gồm các chức năng) và dữ liệu là riêng biệt. Chúng thường chiếm các phần khác nhau của bộ nhớ và chúng được truy cập khác nhau: mã được tải một lần và sau đó chỉ được thực thi (nhưng không được đọc hoặc ghi), trong khi dữ liệu thường được phân bổ và xử lý liên tục và được ghi và đọc, nhưng không bao giờ được thực thi.

    Và vì C có nghĩa là "gần với kim loại", nên có ý nghĩa phản ánh sự khác biệt này trong cú pháp của ngôn ngữ.

  • Cách tiếp cận "hàm chỉ là một giá trị" tạo thành nền tảng của lập trình hàm đã đạt được lực kéo trong các ngôn ngữ phổ biến chỉ tương đối gần đây, bằng chứng là sự ra đời muộn của lambdas trong C ++, C # và Java (2011, 2007, 2014).


12
C # có các hàm ẩn danh - chỉ là lambdas mà không cần suy luận kiểu và cú pháp vụng về - kể từ năm 2005.
Eric Lippert

2
"Từ góc độ kỹ thuật hơn, mã (bao gồm các chức năng) và dữ liệu là riêng biệt" - một số ngôn ngữ, hầu hết các LISP đều không tạo ra sự tách biệt đó và không thực sự đối xử với mã và dữ liệu quá khác nhau. (LISP là ví dụ nổi tiếng nhất, nhưng có một loạt các ngôn ngữ khác như REBOL làm điều này)
Benjamin Gruenbaum

1
@BenjaminGruenbaum Tôi đã nói về mã được biên dịch trong bộ nhớ, không phải về trình độ ngôn ngữ.
Svick

C có các con trỏ hàm, ít nhất là một chút, có thể được coi là một giá trị khác. Một giá trị nguy hiểm để gây rối với chắc chắn, nhưng những điều kỳ lạ đã xảy ra.
Patrick Hughes

@PatrickHughes Vâng, tôi đề cập đến chúng trong điểm thứ hai của tôi. Nhưng con trỏ hàm khá khác với hàm.
Svick

59

Đó là vì điều quan trọng đối với con người là nhận ra rằng các chức năng không chỉ là "thực thể có tên khác". Đôi khi nó có ý nghĩa để thao túng chúng như vậy, nhưng chúng vẫn có thể được nhận ra trong nháy mắt.

Nó thực sự không quan trọng những gì máy tính nghĩ về cú pháp, vì một loạt các ký tự không thể hiểu được là một cỗ máy có thể diễn giải, nhưng điều đó sẽ không thể hiểu được và duy trì cho con người.

Đó thực sự là lý do tương tự như lý do tại sao chúng ta có các vòng lặp trong khi và cho các vòng lặp, chuyển đổi và nếu khác, v.v., mặc dù cuối cùng tất cả chúng đều sôi sục để hướng dẫn so sánh và nhảy. Lý do là bởi vì đó là vì lợi ích của con người duy trì và hiểu mã.

Có các chức năng của bạn là "thực thể có tên khác" theo cách bạn đang đề xuất sẽ làm cho mã của bạn khó nhìn hơn và do đó khó hiểu hơn.


12
Tôi không đồng ý rằng việc xử lý các hàm như các thực thể được đặt tên nhất thiết phải làm cho mã khó hiểu hơn. Điều đó gần như chắc chắn đúng với bất kỳ mã nào tuân theo mô hình thủ tục, nhưng nó có thể không đúng với mã tuân theo mô hình chức năng.
Kyle Strand

29
"Đó thực sự là lý do tương tự như lý do tại sao chúng ta có các vòng lặp trong khi và cho các vòng lặp, chuyển đổi và nếu khác, v.v., mặc dù cuối cùng tất cả chúng đều sôi sục để hướng dẫn so sánh và nhảy. " +1 cho điều đó. Nếu tất cả các lập trình viên đều cảm thấy thoải mái với ngôn ngữ máy, chúng ta sẽ không cần tất cả các ngôn ngữ lập trình ưa thích này (mức cao hay thấp). Nhưng sự thật là: mọi người đều có một mức độ thoải mái khác nhau về mức độ gần với máy họ muốn viết mã của họ. Khi bạn tìm thấy cấp độ của mình, bạn chỉ cần tuân theo cú pháp được cung cấp cho bạn (hoặc viết thông số kỹ thuật và trình biên dịch ngôn ngữ của riêng bạn nếu bạn thực sự bận tâm).
Hoki

12
+1. Một phiên bản ngắn gọn hơn có thể là "Tại sao tất cả các ngôn ngữ tự nhiên phân biệt danh từ với động từ?"
msw

2
"Một loạt các ký tự không thể hiểu được là tốt cho một cỗ máy để giải thích, nhưng điều đó sẽ không thể hiểu được và duy trì" Con người đủ điều kiện để thay đổi cú pháp khiêm tốn như đề xuất trong câu hỏi. Một cách nhanh chóng thích nghi với cú pháp. Đề xuất này là một sự khởi đầu ít triệt để hơn so với quy ước so với cú pháp gọi phương thức OO object.method (args) đã có trong thời đại của nó, nhưng điều này chưa được chứng minh là không thể cho con người hiểu và duy trì. Mặc dù thực tế là trong hầu hết các ngôn ngữ OO, các phương thức không nên được coi là được lưu trữ trong các thể hiện của lớp.
Marc van Leeuwen

4
@MarcvanLeeuwen. Nhận xét của bạn là đúng và có ý nghĩa, nhưng một số nhầm lẫn được kích thích bởi cách bài viết gốc được tái cấu trúc. Thực tế có 2 câu hỏi: (i) Tại sao lại như vậy? và (ii) Tại sao không phải theo cách này thay thế? . Câu trả lời bằng cách whatsisnamegiải quyết nhiều hơn điểm đầu tiên (và cảnh báo về một số nguy cơ loại bỏ những lỗi không an toàn này), trong khi nhận xét của bạn liên quan nhiều hơn đến phần thứ hai của câu hỏi. Thực sự có thể thay đổi cú pháp này (và như bạn đã mô tả, nó đã được thực hiện nhiều lần rồi ...) nhưng nó sẽ không phù hợp với tất cả mọi người (vì oop cũng không phù hợp với tất cả mọi người.
Hoki

10

Bạn có thể thích thú khi biết rằng, từ thời tiền sử, một ngôn ngữ có tên ALGOL 68 đã sử dụng một cú pháp gần với những gì bạn đề xuất. Nhận thấy rằng các định danh hàm bị ràng buộc với các giá trị giống như các định danh khác, bạn có thể trong ngôn ngữ đó khai báo một hàm (hằng số) bằng cú pháp

tên kiểu hàm = ( danh sách tham số ) kiểu kết quả : body ;

Cụ thể ví dụ của bạn sẽ đọc

PROC (INT)INT add one = (INT n) INT: n+1;

Nhận biết sự dư thừa trong đó loại ban đầu có thể được đọc từ RHS của khai báo và là một loại hàm luôn bắt đầu bằng PROC, điều này có thể (và thường sẽ) được ký hợp đồng

PROC add one = (INT n) INT: n+1;

nhưng lưu ý rằng =vẫn còn trước danh sách tham số. Cũng lưu ý rằng nếu bạn muốn một biến chức năng (mà sau đó một giá trị khác của cùng loại chức năng có thể được gán), thì =nên thay thế bằng cách :=đưa ra một trong hai

PROC (INT)INT func var := (INT n) INT: n+1;
PROC func var := (INT n) INT: n+1;

Tuy nhiên trong trường hợp này cả hai hình thức trên thực tế là viết tắt; do mã định danh func varchỉ định tham chiếu đến hàm được tạo cục bộ, nên biểu mẫu được mở rộng hoàn toàn sẽ là

REF PROC (INT)INT func var = LOC PROC (INT)INT := (INT n) INT: n+1;

Dạng cú pháp đặc biệt này rất dễ làm quen, nhưng rõ ràng nó không có lượng lớn người theo các ngôn ngữ lập trình khác. Ngay cả ngôn ngữ lập trình chức năng như Haskell thích phong cách f n = n+1với = sau danh sách tham số. Tôi đoán lý do chủ yếu là tâm lý; sau khi tất cả các nhà toán học thậm chí không thường thích, như tôi đã làm, f = nn + 1 trên f ( n ) = n + 1.

Nhân tiện, cuộc thảo luận ở trên làm nổi bật một sự khác biệt quan trọng giữa các biến và hàm: định nghĩa hàm thường liên kết một tên với một giá trị hàm cụ thể, sau đó không thể thay đổi, trong khi các định nghĩa biến thường đưa ra một định danh với giá trị ban đầu , nhưng một định nghĩa Có thể thay đổi sau này. (Đây không phải là quy tắc tuyệt đối; các biến hàm và hằng số hàm không xảy ra trong hầu hết các ngôn ngữ.) Ngoài ra, trong các ngôn ngữ được biên dịch, giá trị bị ràng buộc trong một định nghĩa hàm thường là hằng số thời gian biên dịch, do đó các lệnh gọi đến hàm có thể là biên dịch bằng một địa chỉ cố định trong mã. Trong C / C ++, điều này thậm chí là một yêu cầu; tương đương với ALGOL 68

PROC (REAL) REAL f = IF mood=sunny THEN sin ELSE cos FI;

không thể được viết bằng C ++ mà không giới thiệu một con trỏ hàm. Loại hạn chế cụ thể này biện minh cho việc sử dụng một cú pháp khác nhau cho các định nghĩa hàm. Nhưng chúng phụ thuộc vào ngữ nghĩa ngôn ngữ và sự biện minh không áp dụng cho tất cả các ngôn ngữ.


1
"Các nhà toán không thích f = nn + 1 trên f ( n ) = n + 1" ... Chưa kể các nhà vật lý, ai muốn viết chỉ f = n + 1 ...
leftaroundabout

1
@leftaroundabout đó là vì là một nỗi đau để viết. Thành thật.
Pierre Arlaud

8

Bạn đã đề cập đến Java và Scala làm ví dụ. Tuy nhiên, bạn đã bỏ qua một thực tế quan trọng: đó không phải là các chức năng, đó là các phương thức. Phương pháp và chức năng là khác nhau cơ bản . Hàm là đối tượng, phương thức thuộc về đối tượng.

Trong Scala, có cả chức năng và phương thức, có những khác biệt sau đây giữa phương thức và chức năng:

  • phương thức có thể chung chung, chức năng không thể
  • các phương thức có thể không có, một hoặc nhiều danh sách tham số, các hàm luôn có chính xác một danh sách tham số
  • phương thức có thể có một danh sách tham số ngầm định, các hàm không thể
  • phương thức có thể có các tham số tùy chọn với các đối số mặc định, các hàm không thể
  • phương thức có thể có các tham số lặp lại, các hàm không thể
  • phương thức có thể có tham số theo tên, hàm không thể
  • phương thức có thể được gọi với các đối số được đặt tên, các hàm không thể
  • Hàm là đối tượng, phương thức không

Vì vậy, đề xuất thay thế của bạn chỉ đơn giản là không hoạt động, ít nhất là cho những trường hợp đó.


2
"Hàm là đối tượng, phương thức thuộc về đối tượng." Điều đó có được áp dụng cho tất cả các ngôn ngữ (như C ++) không?
Svick

9
"Các phương thức có thể có các tham số theo tên, các hàm không thể" - đây không phải là một sự khác biệt cơ bản giữa các phương thức và các hàm, nó chỉ là một sự châm biếm của Scala. Giống với hầu hết (tất cả?) Của những người khác.
dùng253751

1
Các phương thức về cơ bản chỉ là một loại chức năng đặc biệt. -1. Lưu ý rằng Java (và bởi phần mở rộng Scala) không có bất kỳ loại hàm nào khác. "Các hàm" của nó là các đối tượng với một phương thức (trong các hàm chung có thể không phải là các đối tượng hoặc thậm chí các giá trị lớp đầu tiên; chúng có phụ thuộc vào ngôn ngữ không).
Jan Hudec

@JanHudec: Về mặt ngữ nghĩa, Java có cả hàm và phương thức; nó sử dụng thuật ngữ "phương thức tĩnh" khi đề cập đến các hàm, nhưng thuật ngữ đó không xóa được sự phân biệt ngữ nghĩa.
supercat

3

Những lý do mà tôi có thể nghĩ đến là:

  • Trình biên dịch sẽ dễ dàng hơn khi biết những gì chúng ta khai báo.
  • Điều quan trọng là chúng ta phải biết (một cách tầm thường) cho dù đây là hàm hay biến. Các chức năng thường là hộp đen và chúng tôi không quan tâm đến việc triển khai nội bộ của chúng. Tôi không thích kiểu suy luận về kiểu trả về trong Scala, vì tôi tin rằng việc sử dụng hàm có kiểu trả về sẽ dễ dàng hơn: đó thường là tài liệu duy nhất được cung cấp.
  • Và điều quan trọng nhất là chiến lược đám đông sau đây được sử dụng trong việc thiết kế ngôn ngữ lập trình. C ++ được tạo ra để đánh cắp các lập trình viên C và Java được thiết kế theo cách không khiến các lập trình viên C ++ sợ hãi và C # để thu hút các lập trình viên Java. Ngay cả C #, mà tôi nghĩ là một ngôn ngữ rất hiện đại với một đội ngũ tuyệt vời đằng sau nó, đã sao chép một số lỗi từ Java hoặc thậm chí từ C.

2
"Cẩu và C # để thu hút các lập trình viên Java" - điều này thật đáng nghi ngờ, C # giống với C ++ hơn nhiều so với Java. và đôi khi có vẻ như nó được tạo ra một cách có chủ ý không tương thích với Java (không có ký tự đại diện, không có lớp bên trong, không có kiểu trả về kiểu đồng biến)
Sarge Borsch

1
@SargeBorsch Tôi không nghĩ rằng nó cố tình không tương thích với Java, tôi khá chắc chắn rằng nhóm C # chỉ đơn giản là cố gắng làm đúng hoặc làm tốt hơn, tuy nhiên bạn muốn xem xét nó. Microsoft đã viết Java & JVM của riêng họ và đã bị kiện. Vì vậy, nhóm C # có khả năng rất có động lực để làm lu mờ Java. Nó chắc chắn là không tương thích, và tốt hơn cho nó. Java đã bắt đầu với một số hạn chế cơ bản và tôi rất vui vì nhóm C # đã chọn, ví dụ, để tạo ra các tổng quát khác nhau (hiển thị trong hệ thống loại chứ không chỉ là đường).
codenheim

2

Xoay quanh câu hỏi, nếu một người không quan tâm đến việc cố gắng chỉnh sửa mã nguồn trên máy bị hạn chế RAM hoặc giảm thiểu thời gian để đọc nó khỏi đĩa mềm, thì có gì sai khi sử dụng từ khóa?

Chắc chắn là nó dễ đọc x=y+zhơn store the value of y plus z into x, nhưng điều đó không có nghĩa là các ký tự dấu chấm câu vốn đã "tốt" hơn từ khóa. Nếu biến i, jkInteger, và xReal, xem xét các dòng sau trong Pascal:

k := i div j;
x := i/j;

Dòng đầu tiên sẽ thực hiện phân chia số nguyên rút ngắn, trong khi dòng thứ hai sẽ thực hiện phân chia số thực. Sự khác biệt có thể được thực hiện một cách độc đáo vì Pascal sử dụng divnhư toán tử chia số nguyên của nó, thay vì cố gắng sử dụng dấu chấm câu đã có mục đích khác (chia số thực).

Mặc dù có một vài bối cảnh trong đó có thể hữu ích để làm cho một định nghĩa hàm ngắn gọn (ví dụ lambda được sử dụng như một phần của biểu thức khác), các hàm thường được coi là nổi bật và dễ nhận biết bằng mắt như các hàm. Trong khi có thể làm cho sự khác biệt trở nên tinh tế hơn nhiều và không sử dụng gì ngoài các ký tự dấu chấm câu, điều gì sẽ là điểm? Nói Function Foo(A,B: Integer; C: Real): Stringcho biết rõ tên của hàm là gì, tham số mà nó mong đợi và hàm trả về. Có lẽ người ta có thể rút ngắn nó bằng sáu hoặc bảy ký tự bằng cách thay thế Functionbằng một số ký tự dấu chấm câu, nhưng những gì sẽ đạt được?

Một điều cần lưu ý là có một sự khác biệt cơ bản nhất giữa một khai báo sẽ luôn liên kết một tên với một phương thức cụ thể hoặc một ràng buộc ảo cụ thể và một biến tạo ra một biến ban đầu xác định một phương thức hoặc ràng buộc cụ thể, nhưng có thể được thay đổi trong thời gian chạy để xác định khác. Vì đây là những khái niệm rất khác nhau về ngữ nghĩa trong hầu hết các khung thủ tục, nên có nghĩa là chúng nên có cú pháp khác nhau.


3
Tôi không nghĩ câu hỏi này là về sự đồng nhất. Đặc biệt là vì ví dụ void f() {}thực sự ngắn hơn so với lambda tương đương trong C ++ ( auto f = [](){};), C # ( Action f = () => {};) và Java ( Runnable f = () -> {};). Sự đồng nhất của lambdas đến từ suy luận kiểu và bỏ sót return, nhưng tôi không nghĩ điều đó liên quan đến những gì câu hỏi này hỏi.
Svick

2

Chà, lý do có thể là, những ngôn ngữ đó không đủ chức năng, có thể nói như vậy. Nói cách khác, bạn hiếm khi xác định các chức năng. Vì vậy, việc sử dụng một từ khóa phụ là chấp nhận được.

Trong các ngôn ngữ của di sản ML hoặc Miranda, OTOH, bạn xác định các chức năng hầu hết thời gian. Nhìn vào một số mã Haskell, ví dụ. Đó thực sự là một chuỗi các định nghĩa hàm, nhiều trong số chúng có các hàm cục bộ và các hàm cục bộ của các hàm cục bộ đó. Do đó, một từ khóa thú vị trong Haskell sẽ là một sai lầm lớn như yêu cầu một tuyên bố khẳng định bằng một ngôn ngữ bắt buộc phải bắt đầu bằng gán . Phân công nguyên nhân có lẽ là do tuyên bố thường xuyên nhất.


1

Cá nhân, tôi thấy không có lỗ hổng chết người trong ý tưởng của bạn; bạn có thể thấy rằng nó khó hơn bạn mong đợi để diễn đạt một số điều nhất định bằng cách sử dụng cú pháp mới của bạn và / hoặc bạn có thể thấy rằng bạn cần sửa đổi nó (thêm các trường hợp đặc biệt khác và các tính năng khác, v.v.), nhưng tôi nghi ngờ bạn sẽ thấy mình cần phải từ bỏ ý tưởng hoàn toàn.

Cú pháp bạn đã đề xuất trông ít nhiều giống như một biến thể của một số kiểu ký hiệu đôi khi được sử dụng để thể hiện các hàm hoặc loại hàm trong toán học. Điều này có nghĩa là, giống như tất cả các ngữ pháp, nó có thể sẽ hấp dẫn nhiều người lập trình hơn những người khác. (Là một nhà toán học, tôi thích nó.)

Tuy nhiên, bạn cần lưu ý rằng trong hầu hết các ngôn ngữ, defcú pháp kiểu (tức là cú pháp truyền thống) hành vi khác với phép gán biến tiêu chuẩn.

  • Trong CC++gia đình, các hàm thường không được coi là "đối tượng", tức là các khối dữ liệu được nhập sẽ được sao chép và đưa vào ngăn xếp và không có gì. (Có, bạn có thể có các con trỏ hàm, nhưng chúng vẫn trỏ đến mã thực thi, không phải là "dữ liệu" theo nghĩa thông thường.)
  • Trong hầu hết các ngôn ngữ OO, có xử lý đặc biệt cho các phương thức (nghĩa là các hàm thành viên); nghĩa là, chúng không chỉ là các hàm được khai báo bên trong phạm vi của một định nghĩa lớp. Sự khác biệt quan trọng nhất là đối tượng mà phương thức đang được gọi thường được truyền dưới dạng tham số đầu tiên ẩn cho phương thức. Python làm cho điều này rõ ràng với self(bằng cách này, thực tế không phải là một từ khóa; bạn có thể đặt bất kỳ định danh hợp lệ nào làm đối số đầu tiên của một phương thức).

Bạn cần xem xét liệu cú pháp mới của bạn có chính xác (và hy vọng bằng trực giác) đại diện cho những gì trình biên dịch hoặc trình thông dịch đang thực sự làm hay không. Nó có thể giúp đọc lên, giả sử, sự khác biệt giữa lambdas và phương pháp trong Ruby; điều này sẽ cho bạn ý tưởng về mô hình chức năng chỉ là dữ liệu của bạn khác với mô hình thủ tục OO / thủ tục điển hình.


1

Đối với một số ngôn ngữ chức năng không phải là giá trị. Trong một ngôn ngữ như vậy để nói rằng

val f = << i : int  ... >> ;

là một định nghĩa hàm, trong khi

val a = 1 ;

khai báo một hằng số, gây nhầm lẫn bởi vì bạn đang sử dụng một cú pháp có nghĩa là hai điều.

Các ngôn ngữ khác, chẳng hạn như ML, Haskell và Scheme coi các hàm là giá trị của lớp 1, nhưng cung cấp cho người dùng một cú pháp đặc biệt để khai báo các hằng số có giá trị của hàm. * Họ đang áp dụng quy tắc "sử dụng hình thức rút ngắn". Tức là nếu một cấu trúc là cả phổ biến và dài dòng, bạn nên cung cấp cho người dùng một tốc ký. Nó không phù hợp để cung cấp cho người dùng hai cú pháp khác nhau có nghĩa chính xác cùng một điều; đôi khi sự thanh lịch nên được hy sinh để tiện ích.

Nếu, trong ngôn ngữ của bạn, các hàm là lớp 1, vậy thì tại sao bạn không thử tìm một cú pháp đủ ngắn gọn để bạn không bị cám dỗ khi tìm một cú pháp cú pháp?

-- Chỉnh sửa --

Một vấn đề khác không ai đưa ra (chưa) là đệ quy. Nếu bạn cho phép

{ 
    val f = << i : int ... g(i-1) ... >> ;
    val g = << j : int ... f(i-1) ... >> ;
    f(x)
}

và bạn cho phép

{
    val a = 42 ;
    val b = a + 1 ;
    a
} ,

nó làm theo mà bạn nên cho phép

{
    val a = b + 1 ; 
    val b = a - 1 ;
    a
} ?

Trong một ngôn ngữ lười biếng (như Haskell), không có vấn đề ở đây. Trong một ngôn ngữ về cơ bản không có kiểm tra tĩnh (như LISP), không có vấn đề gì ở đây. Nhưng trong một ngôn ngữ háo hức được kiểm tra tĩnh, bạn phải cẩn thận về cách quy tắc kiểm tra tĩnh được xác định, nếu bạn muốn cho phép hai cái đầu tiên và cấm cái cuối cùng.

- Kết thúc chỉnh sửa -

* Có thể lập luận rằng Haskell không thuộc danh sách này. Nó cung cấp hai cách để khai báo một hàm, nhưng theo cả hai nghĩa, là khái quát hóa cú pháp để khai báo các hằng số của các loại khác


0

Điều này có thể hữu ích trên các ngôn ngữ động trong đó loại không quan trọng, nhưng nó không thể đọc được bằng các ngôn ngữ được nhập tĩnh, nơi bạn luôn muốn biết loại biến của mình. Ngoài ra, trong các ngôn ngữ hướng đối tượng, điều quan trọng là phải biết loại biến của bạn, để biết nó hỗ trợ các hoạt động nào.

Trong trường hợp của bạn, một hàm có 4 biến sẽ là:

(int, long, double, String => int) addOne = (x, y, z, s) => {
  return x + 1;
}

Khi tôi nhìn vào tiêu đề hàm và thấy (x, y, z, s) nhưng tôi không biết các loại của các biến này. Nếu tôi muốn biết loại znào là tham số thứ ba, tôi sẽ phải xem phần đầu của hàm và bắt đầu đếm 1, 2, 3 và sau đó để xem loại đó là gì double. Theo cách trước đây tôi nhìn trực tiếp và thấy double z.


3
Tất nhiên, có một thứ tuyệt vời gọi là suy luận kiểu, cho phép bạn viết một cái gì đó giống như var addOne = (int x, long y, double z, String s) => { x + 1 }bằng một ngôn ngữ gõ tĩnh không theo kiểu bạn chọn (ví dụ: C #, C ++, Scala). Ngay cả suy luận kiểu cục bộ rất hạn chế được sử dụng bởi C # là hoàn toàn đủ cho việc này. Vì vậy, câu trả lời này chỉ đơn thuần là chỉ trích một cú pháp cụ thể không rõ ràng ngay từ đầu và không thực sự được sử dụng ở bất cứ đâu (mặc dù cú pháp của Haskell có một vấn đề rất giống nhau).
amon

4
@amon Câu trả lời của anh ấy có thể chỉ trích cú pháp, nhưng nó cũng chỉ ra rằng cú pháp có mục đích trong câu hỏi có thể không "tự nhiên" với mọi người.

1
@amon Sự hữu ích của suy luận kiểu không phải là một điều tuyệt vời cho tất cả mọi người. Nếu bạn đọc mã thường xuyên hơn mà bạn viết mã, thì gõ suy luận là xấu vì bạn phải suy luận ra trong đầu bạn khi đọc mã; và cách đọc loại này nhanh hơn nhiều so với việc thực sự nghĩ về loại nào được sử dụng.
m3th0dman

1
Những lời chỉ trích có vẻ hợp lệ với tôi. Đối với Java, OP dường như nghĩ rằng [đầu vào, đầu ra, tên, đầu vào] là một hàm đơn giản / rõ ràng hơn def đơn giản là [đầu ra, tên, đầu vào]? Như @ m3th0dman tuyên bố, với chữ ký dài hơn, cách tiếp cận 'sạch hơn' của OP trở nên rất dài dòng.
Michael

0

Có một lý do rất đơn giản để có sự phân biệt như vậy trong hầu hết các ngôn ngữ: cần phải phân biệt đánh giákhai báo . Ví dụ của bạn là tốt: tại sao không thích các biến? Vâng, các biểu thức biến được đánh giá ngay lập tức.

Haskell có một mô hình đặc biệt trong đó không có sự phân biệt giữa đánh giá và khai báo, đó là lý do tại sao không cần một từ khóa đặc biệt.


2
Nhưng một số cú pháp cho các hàm lambda cũng giải quyết được điều này, vậy tại sao không sử dụng nó?
Svick

0

Các hàm được khai báo khác với nghĩa đen, đối tượng, v.v. trong hầu hết các ngôn ngữ vì chúng được sử dụng khác nhau, gỡ lỗi khác nhau và gây ra các nguồn lỗi tiềm ẩn khác nhau.

Nếu một tham chiếu đối tượng động hoặc một đối tượng có thể thay đổi được truyền cho một hàm, hàm đó có thể thay đổi giá trị của đối tượng khi nó chạy. Loại hiệu ứng phụ này có thể gây khó khăn cho việc theo dõi chức năng sẽ làm gì nếu nó được lồng trong một biểu thức phức tạp và đây là một vấn đề phổ biến trong các ngôn ngữ như C ++ và Java.

Xem xét gỡ lỗi một số loại mô-đun hạt nhân trong Java, trong đó mọi đối tượng đều có hoạt động toString (). Mặc dù có thể hy vọng rằng phương thức toString () sẽ khôi phục đối tượng, nhưng nó có thể cần phải tháo rời và lắp lại đối tượng để dịch giá trị của nó sang đối tượng String. Nếu bạn đang cố gắng gỡ lỗi các phương thức mà toString () sẽ gọi (trong kịch bản hook-and-template) để thực hiện công việc của nó và vô tình làm nổi bật đối tượng trong cửa sổ biến của hầu hết các IDE, nó có thể làm hỏng trình gỡ lỗi. Điều này là do IDE sẽ cố gắng toString () đối tượng gọi chính mã mà bạn đang trong quá trình gỡ lỗi. Không có giá trị nguyên thủy nào làm tào lao như thế này vì ý nghĩa ngữ nghĩa của các giá trị nguyên thủy được xác định bởi ngôn ngữ, không phải lập trình viên.

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.