Giá trị trả về được đặt bằng cách gán cho tên của hàm trong (các) ngôn ngữ nào?
Trong câu hỏi Stack Overflow này , mã gốc đã mắc lỗi khi sử dụng tên hàm làm biến và gán giá trị trả về cho nó. Một người bình luận đã đề cập rằng anh ta đã từng sử dụng một ngôn ngữ mà đây là cách bạn trả về giá trị từ các hàm. Nhận xét có nội dung "Tôi biết tôi đã từng sử dụng một ngôn ngữ mà giá trị trả về của một hàm cần được gán cho tên của hàm. Nó quá cổ và lỗi thời, tôi thậm chí không thể nhớ đó là ngôn ngữ nào."
Điều đó nghe có vẻ quen thuộc với tôi, nhưng tôi cũng không thể nhớ đó là ngôn ngữ gì.
Có ai có trí nhớ tốt hơn chúng tôi và có thể cho chúng tôi biết đó là ngôn ngữ nào không?
Trả lời
Pascal làm được điều này, tôi không biết những người khác. Không biết liệu việc luyện tập có tiến triển với các ngôn ngữ Wirth khác hay không.
Các ngôn ngữ trong họ Visual Basic thực hiện chính xác điều này. Điều này bao gồm VBScript, VBA, Visual Basic và các phiên bản cũ hơn. Tôi tin rằng chúng kế thừa "tính năng" từ QBASIC. Ví dụ
Public Function AddTwo(something as Integer)
AddTwo = something + 2
End Function
Fortran, chắc chắn:
PROGRAM TRIANG
WRITE(UNIT=*,FMT=*)'Enter lengths of three sides:'
READ(UNIT=*,FMT=*) SIDEA, SIDEB, SIDEC
WRITE(UNIT=*,FMT=*)'Area is ', AREA3(SIDEA,SIDEB,SIDEC)
END
FUNCTION AREA3(A, B, C)
*Computes the area of a triangle from lengths of sides
S = (A + B + C)/2.0
AREA3 = SQRT(S * (S-A) * (S-B) * (S-C))
END
(từ Hướng dẫn lập trình viên chuyên nghiệp của Clive G. Page cho Fortran77 ).
Nó cũng được định nghĩa theo cách đó trong tiêu chuẩn Fortran ANSI X 3.9 1966 Fortran 66 .
Những ngôn ngữ sớm nhất mà tôi có thể tìm thấy là FORTRAN II và ALGOL 58, cả hai đều được xuất bản vào cùng năm 1958; mặc dù bản gốc FORTRAN (1956) cũng có thể được đưa vào.
Đối với FORTRAN, trang đầu tiên của chương hướng dẫn sử dụng bao gồm các chức năng chứa ví dụ này (trang 27):
FUNCTION AVRG (ALIST, N)
DIMENSION ALIST (500)
SUM = ALIST (1)
DO 10 I=2, N
SUM = SUM + ALIST (I)
AVRG = SUM / FLOATF (N)
RETURN
END (2, 2, 2, 2, 2)
FORTRAN II cũng bao gồm một cú pháp hàm khác (trang 10), định nghĩa hàm một dòng, được kế thừa từ bộ tiền xử lý của nó:
FIRSTF(X) = A*X + B
Không khó để thấy cú pháp trước đây là một phần mở rộng tự nhiên của cú pháp sau, đến lượt nó đến từ cách sử dụng toán học.
ALGOL 58 , tương tự như FORTRAN, xác định cả hai 'chức năng' một dòng:
Một khai báo hàm khai báo một biểu thức đã cho là một hàm của một số biến nhất định của nó. Qua đó, khai báo đưa ra (đối với một số hàm đơn giản nhất định) quy tắc tính toán để gán giá trị cho hàm (xem hàm ) bất cứ khi nào hàm này xuất hiện trong một biểu thức.
Dạng: Δ ~ I n (I, I, ~, I): = E trong đó I là các định danh và E là một biểu thức, trong số các thành phần của nó, có thể chứa các biến đơn giản được đặt tên bởi các định danh xuất hiện trong dấu ngoặc đơn.
và 'thủ tục', tương đương với định nghĩa ngày nay về hàm (ít nhất là trong các ngôn ngữ lập trình mệnh lệnh / thủ tục). Giá trị trả về được chỉ ra như sau (tr. 19):
Đối với mỗi thủ tục đầu ra đơn lẻ I (P i ) được liệt kê trong tiêu đề, một giá trị phải được gán trong thủ tục bằng câu lệnh gán “I: = E” trong đó tôi là định danh đặt tên cho thủ tục đó.
Các cú pháp này sau đó đã được sử dụng bởi một số phương ngữ của BASIC (ở dạng DEF FN
và sau này FUNCTION
) và con cháu của ALGOL là Pascal: trong các trình biên dịch Pascal của Borland, gán cho tên hàm là cú pháp duy nhất được hỗ trợ trước khi Result
biến trong Delphi 1.0 ra đời.
Nó có lẽ là Pascal mà người bình luận được đề cập nhớ; một số trường đại học vẫn dạy lập trình trong đó, và thường bám vào bản gốc, chuẩn đa dạng, thay vì các phương ngữ mở rộng hiện đại như Object Pascal. (Đây thực sự không phải là một phần của câu hỏi, nhưng tôi cho rằng sự hiểu lầm của người hỏi StackOverflow cũng xuất phát từ đó.)
TL; DR:
Tôi muốn nói, rất có thể đó là PASCAL mà bạn còn nhớ, vì nó khá phổ biến vào đầu những năm 80, được sử dụng trong các khóa học Đại học từ những năm 80 đến những năm 90 và vẫn có một số học bổng sau đó, đáng chú ý nhất là Delphi.
Một số lịch sử
Ý tưởng cơ bản là tên hàm không chỉ đã được đặt trước, vì vậy không cần phải nghĩ ra bất kỳ điều gì khác biệt, và sử dụng nó là một tuyên bố rõ ràng rằng đây là kết quả. Nó cũng đơn giản hóa thiết kế trình biên dịch, vì một mục dữ liệu chuyên dụng có thể được cấp phát trong quy ước gọi.
Về cơ bản có hai dòng di sản, FORTRAN và ALGOL.
Đối với cả hai con cháu của họ đã giữ nó, như
- một số biến thể CƠ BẢN từ FORTRAN và
- Pascal và Modula từ ALGOL.
Những người khác đã bỏ nó, như ALGOL theo dõi
- BCPL, đã giới thiệu
return()
cú pháp,
khá phổ biến ngày nay vì C đã lấy nó từ BCPL.
Ý tưởng ngôn ngữ giống như gen nhảy giữa các vật chủ. Ví dụ ADA, theo nhiều cách, một đứa cháu ALGOL / PASCAL, cũng chuyển sang sử dụng một return
phần tử.
Ông ngoại FORTRAN, trong nhiều năm, đã thay đổi cách trả về kết quả hàm.
- Ban đầu, kết quả của một hàm được gán cho mã định danh của hàm
- với FORTRAN 90, định nghĩa rõ ràng về tên trả về trong phần đầu hàm đã được giới thiệu.
Trong khi về cơ bản đây chỉ là đường cú pháp, nó có một sự thay đổi trong phong cách. Lý do được áp dụng là với các cấu trúc đệ quy Foo = Foo(x-1)
sẽ trông kỳ lạ. Nhưng tôi đoán đó là cách giải thích.
Điều thú vị ở đây là FORTRAN II năm 1958 đã giới thiệu một RETURN
tuyên bố trong nỗ lực bổ sung lập trình thủ tục, nhưng cách sử dụng của nó chỉ đơn giản là để trả lại thực thi cho người gọi, giá trị trả về phải được đặt riêng biệt.
Fortran đã sử dụng cú pháp này, từ phiên bản đầu tiên có các chức năng cho đến Fortran 2008 và hơn thế nữa.
Tuy nhiên Fortran 2008 có một tùy chọn (thậm chí còn khó hiểu hơn?), Nơi bạn có thể khai báo một tên biến khác được sử dụng để trả về một giá trị hàm! Ví dụ
function xyz(argument) result(answer)
...
answer = 42
...
end function xyz
thay vì kiểu cũ
...
xyz = 42
...
Algol 60 cho một.
Đây là những từ có liên quan từ Báo cáo sửa đổi về Ngôn ngữ thuật toán Algol 60 .
5.4.4. Giá trị của người chỉ định chức năng.
Đối với một khai báo thủ tục để xác định giá trị của một bộ chỉ định hàm, trong cơ quan khai báo thủ tục, phải xảy ra một hoặc nhiều câu lệnh gán rõ ràng với mã định danh thủ tục ở một phần bên trái; ít nhất một trong số này phải được thực thi và kiểu được liên kết với mã định danh thủ tục phải được khai báo thông qua sự xuất hiện của bộ khai báo kiểu như là ký hiệu đầu tiên của khai báo thủ tục. Giá trị cuối cùng được chỉ định như vậy được sử dụng để tiếp tục đánh giá biểu thức trong đó hàm chỉ định xuất hiện.
Bất kỳ sự xuất hiện nào của mã định danh thủ tục trong phần thân của thủ tục khác với phần bên trái của câu lệnh gán biểu thị việc kích hoạt thủ tục.
Câu cuối cùng rất quan trọng - nó cho thấy rằng tên của thủ tục kiểu (hàm) không được coi là 'giống như' một biến trong phần thân của thủ tục (hàm); đúng hơn, nó chỉ là nhiệm vụ được phân biệt đặc biệt.
Trong Algol 60, một lệnh gọi đến một hàm không nhận đối số sẽ không được theo sau bởi dấu ngoặc đơn trống: do đó n := read
thay vì n := read()
.
Câu cuối cùng cũng nổi tiếng là câu có các thủ tục đệ quy vào ngôn ngữ. Nhưng đó không phải là điều sai trái đối với câu trả lời này.
BASIC là một ngôn ngữ khác có các hàm trong đó một số phương ngữ đã sử dụng phép gán cho tên hàm để cung cấp giá trị trả về. Các phương ngữ đầu tiên tương tự như các hàm đơn dòng của Fortran:
DEF FND(x) = x*x
Nhưng các phương ngữ sau này cho phép các biến thể phức tạp hơn, tương tự như các hàm đa dòng của Fortran :
DEF FNPeekWord& (A&)
FNPeekWord& = PEEK(A&) + 256& * PEEK(A& + 1)
END DEF
MATLAB / Octave cũng thực hiện điều này.
Đó là từ năm 1984; vì vậy không phải là cũ như một số người khác.
Nó có lẽ đang bắt chước Fortran, vì nó đã được coi là một công cụ cấp cao. Trên đầu trang của các thư viện Fortran như Linpack và Eispack.
Tôi tin rằng SNOBOL4 đã làm được điều này. http://berstis.com/greenbook.pdf
Sau đây là một ví dụ về định nghĩa và sử dụng một hàm để tính thừa số của các số:
DEFINE('FACT(N)') :(SKIPFCN) * Set value to 1 FACT FACT = 1 * Return 1 if N<2 * Return N*((N-1)!) with recursive call FACT = GT(N,1) FACT(N - 1) * N :(RETURN) SKIPFCN OUTPUT = '5 factorial is ' FACT(5)
http://berstis.com/s4ref/prim3e.htm
Verilog (1995/2001) cũng trả về bằng cách gán cho biến ngầm định. SystemVerilog đã thêm câu lệnh "return" nhưng phép gán cổ điển vẫn có sẵn.
Haskell (từ 1990) cũng làm điều này:
doubleMe x = x + x
xác định một hàm doubleMe
của một tham số x
và gán phần thân hàm x+x
cho nó, hãy xem bài học tuyệt vời Learn You A Haskell For Great Good
Pascal là một trong những mà cá nhân tôi đã sử dụng để làm điều đó. Lisp phổ biến kinda-sorta-but-not-thực sự làm được điều đó, trong đó các giá trị trả về hầu như luôn ngầm định (ví dụ: mọi câu lệnh đều có một giá trị và giá trị cuối cùng trong một khối là giá trị trả về của khối), vì vậy bạn rất hiếm khi thấy một tuyên bố trở lại rõ ràng, nhưng khi bạn cần phải trả lại một giá trị và không thể sử dụng cách tiềm ẩn, cách để làm điều đó là sử dụng RETURN-FROM
[*] tuyên bố, như vậy: (return-from function-name value)
.
[*] Cũng có một RETURN
câu lệnh, nhưng nó là cách viết tắt của (return-from nil value)
và sẽ không có tác dụng tạo VALUE
giá trị của hàm mà nó được thực thi. Đó là một cạm bẫy lớn cho những người mới đến từ C và các hậu duệ của nó.