Tại sao nhiều giá trị trả lại không phải là một điều phổ biến?


7

Tôi muốn hỏi một câu hỏi về nhiều giá trị trả về, tại sao cấu trúc này không thích hợp trong các ngôn ngữ lập trình (khó khăn về khái niệm và / hoặc kỹ thuật). Tôi đã nghe một vài điều về khung stack và cách chúng dự trữ bộ nhớ cho giá trị trả về và giá trị trả về biến đổi có thể khiến vấn đề này trở nên rắc rối, nhưng nếu ai đó có thể giải thích điều này tốt hơn, điều này sẽ được đánh giá cao.

Trong các ngôn ngữ conctanetative (như FORTH), có nhiều giá trị trả về từ một hàm là một điều phổ biến và rất hữu ích, và tôi tưởng tượng một cái gì đó như thế này trong các ngôn ngữ giống như java / c cũng hữu ích (một ví dụ rất cơ bản):

x, y = multRet(5);
multret(int x) {
    return x+1;
    return x+2;
    exit;
}

Để làm rõ: Tôi không hỏi làm thế nào để trả về nhiều giá trị (đây là một câu hỏi đã biết với câu trả lời đã biết), nhưng tôi muốn làm rõ về lý do tại sao thực tiễn này không phổ biến trong các ngôn ngữ lập trình.


4
Một lời giải thích có thể là, nếu bạn cần trả về nhiều giá trị, bạn luôn có thể làm điều đó bằng cách trả về một cấu trúc hoặc danh sách. Tại sao làm phức tạp ngôn ngữ và trình biên dịch?
David Richerby

4
Tôi không nghĩ câu hỏi là có thể trả lời được. Đầu tiên, đây chỉ là vấn đề của hương vị. Thứ hai, tôi nghĩ tiền đề rằng nó "không phổ biến" là sai. Có một số lượng lớn các ngôn ngữ hỗ trợ các bộ dữ liệu trả về, và đã có từ những năm 1970 và thực tiễn hỗ trợ các bộ dữ liệu trả về dường như đang trở nên phổ biến vì tất cả các ngôn ngữ lập trình hệ thống phổ biến được thiết kế gần đây (Swift, Go, Rust) đều hỗ trợ các bộ dữ liệu trở lại .
Logic lang thang

2
Tôi đồng ý với @DavidR Richby; một tính năng như vậy sẽ là dư thừa và có ngữ nghĩa khó khăn / mờ ám (điều gì xảy ra với i=0; while (true) { return i; i+=1 }?). Lưu ý rằng một số ngôn ngữ chức năng cung cấp bộ dữ liệu, một sự thay thế tốt hơn.
Raphael

1
@WanderingLogic Một tuple vẫn là một giá trị, được trả về bởi một returncâu lệnh. Khi trả về các hàm / lambdas, bạn có thể có một điểm.
Raphael

1
Tôi không đồng ý rằng câu hỏi này dựa trên ý kiến: các ý kiến ​​cho đến nay cho thấy có câu trả lời kỹ thuật. Nếu bạn cảm thấy rằng phần "tại sao" của câu hỏi quá chủ quan, nó có thể dễ dàng được chỉnh sửa thành "Những lợi thế và bất lợi của việc cho phép nhiều giá trị trả về là gì?"
David Richerby

Câu trả lời:


7

Các hàm thường được coi là trả về một giá trị duy nhất - trong toán học. Tất nhiên một số giá trị tạo thành một tuple, nhưng thông thường trong toán học, chúng ta đang xử lý các hàm có giá trị thực. Tôi nghi ngờ rằng nguồn gốc của các hàm này là lý do tại sao nhiều giá trị trả về không quá phổ biến. Điều đó nói rằng, thật dễ dàng để mô phỏng chúng theo nhiều cách (bằng cách trả về một tuple hoặc bằng cách có một vài tham số đầu ra, tức là thông số được truyền qua tham chiếu) và một số ngôn ngữ hiện đại phổ biến (như Python) thực sự hỗ trợ cho việc xây dựng này.


4

Có nhiều giá trị trả lại tốt hơn . Thiếu chúng là thiết kế xấu, đơn giản và đơn giản. Tuy nhiên, chúng ta nên làm sáng tỏ quan niệm sai lầm rằng một điều như vậy thậm chí tồn tại trong ý nghĩa mà bạn đang nghĩ đến.

Trong các ngôn ngữ dựa trên lambda-compus (ngôn ngữ lập trình tiền thân), ví dụ ML-Family, Haskell, một hàm (trừu tượng lambda) chỉ chấp nhận một đối số duy nhất và "trả về" (chính thức hơn, đánh giá) một kết quả duy nhất. Vậy làm thế nào chúng ta có thể tạo một hàm dường như lấy hoặc trả về nhiều giá trị?

Câu trả lời là chúng ta có thể "kết dính" các giá trị với nhau bằng cách sử dụng các loại sản phẩm . Nếu AB là cả hai loại, thì loại sản phẩmMột×B chứa tất cả các bộ dữ liệu của mẫu (một,b), Ở đâu một:Mộtb:B.

Do đó, để tạo một hàm multiplynhân hai số nguyên với nhau, chúng ta cung cấp cho nó loại multiply: int * int -> int. Nó chấp nhận một đối số duy nhất xảy ra là một tuple. Tương tự, một hàm splitchia danh sách thành hai sẽ có kiểusplit: a list -> a list * a list .

Có thể 30 năm trước thực hiện là một mối quan tâm hợp lệ trong việc bao gồm một tính năng như vậy. Theo quy ước gọi C là cedl (và chỉ là, một quy ước - không có vị thần nào phong chức nó), giá trị trả về thường được lưu trữ trong một thanh ghi đặc biệt EAX. Chắc chắn, điều này là cực kỳ nhanh để đọc và viết, vì vậy ý ​​tưởng có một số giá trị.

Nhưng nó quá hạn chế, như bạn chỉ ra, và không có lý do gì chúng ta cần phải tuân theo quy tắc này ngày nay. Ví dụ: thay vào đó, chúng tôi có thể sử dụng chính sách sau: đối với một số lượng nhỏ giá trị trả về, hãy sử dụng một bộ các thanh ghi được chỉ định (hiện tại chúng tôi có nhiều thanh ghi để sử dụng hơn so với trong thập niên 80). Đối với dữ liệu lớn hơn, chúng ta có thể trả về một con trỏ tới một vị trí trong bộ nhớ. Tôi không biết những gì tốt nhất; Tôi không phải là một nhà văn biên dịch. Nhưng quy ước gọi C cổ xưa sẽ không có ảnh hưởng đến ngữ nghĩa của các ngôn ngữ hiện đại.


1

Cú pháp là không cần thiết khi các mẫu đơn giản thực hiện công việc tốt hơn và cú pháp đề xuất của bạn sẽ gây nhầm lẫn trong lịch sử cho nhiều người. Điều này có nghĩa là cú pháp của bạn khó học và sẽ dẫn đến các vấn đề khi mọi người chuyển đổi giữa ngôn ngữ của bạn và người khác.

Mẫu đơn giản giải quyết vấn đề này là xác định Biến có tên "returnValue" khi bắt đầu phương thức của bạn và sau đó gán giá trị cho nó. Nếu bạn cần nhiều giá trị, sau đó chỉ cần sử dụng một bộ sưu tập một số loại và thêm các giá trị thay thế. Sau đó trả lại Biến này. Vấn đề được giải quyết.

Cú pháp của bạn có nghĩa là theo mặc định bạn sẽ phải xử lý các bộ sưu tập. Điều này gây khó chịu cho hàm chỉ trả về 1 giá trị.

Và nếu ai đó làm điều gì sai? Ví dụ.

x, y = multRet(5);
multret(int x) {
    return x+1;
    exit;
}

hoặc là

x = multRet(5);
multret(int x) {
    return x+1;
    return x+1;
    exit;
}

Là trình biên dịch có nghĩa vụ để bắt nó? Nếu bạn có một vòng lặp thì sao?

Tốt nhất đây sẽ là đường cú pháp. Một cái gì đó không thêm các tính năng mới mà thay vào đó chỉ làm cho các mẫu hiện tại thoải mái hơn và mã ngắn hơn.

Tệ nhất điều này sẽ dẫn đến lỗi khủng khiếp, khủng khiếp trong mã. Ngoại lệ con trỏ Null trong khối lượng.


1
Trình biên dịch có thể bắt được nhiều giá trị trả về tốt. Đó là những gì ngôn ngữ lập trình Go làm.
slebetman

1

Nó chủ yếu là vì lý do cú pháp; trong ví dụ của riêng bạn, câu lệnh hoàn trả đầu tiên sẽ trả về một giá trị và trả về và không bao giờ đạt được câu lệnh return thứ hai.

Ngày nay có một vài ngôn ngữ cho phép trả về một tuple, đó là zero, một hoặc nhiều giá trị của các loại tùy ý, được đóng gói cùng nhau thành một giá trị. Tuy nhiên, họ đang trả về một giá trị, chỉ là một giá trị phức tạp hơn một chút. Thông thường các ngôn ngữ như vậy cũng sẽ có cách gán một tuple bằng 0, một hoặc nhiều biến khớp, thường là một cách dễ dàng để bỏ qua một số phần tử của tuple. Điều này có thể trông như thế này:

Gán tuple cho một tuple khác:

x = multRet(5); // x is a variable of type tuple (int, int)
multret(int x) {
    return (x+1, x+2);
}

(x, y) = multRet(5); // x, y are variables of type int
multret(int x) {
    return (x+1, x+2);
}

(x, _) = multRet(5); // x is a variable of type int, second component ignored. 
multret(int x) {
    return (x+1, x+2);
}

Cú pháp đó sẽ không tương thích với C, trong đó (x, y) là biểu thức dấu phẩy trong ngoặc đơn. Mặt khác, việc nhận hai giá trị dễ dàng hơn nhiều so với trong C, khi bạn chuyển hai giá trị bằng cách sử dụng con trỏ hoặc bạn khai báo một cấu trúc cho mục đích này, điền vào nó và trả về nó. Sử dụng con trỏ, ví dụ thứ ba mà người gọi chỉ quan tâm đến một giá trị là một nỗi đau, vì vậy phương pháp trả lại bộ dữ liệu này rất hữu ích.


Go là một ví dụ trong đó các giá trị trả về là các giá trị riêng biệt thay vì một tuple. Trong Go, bạn có thể thực hiện các ví dụ thứ hai và thứ ba nhưng ví dụ đầu tiên sẽ dẫn đến lỗi thời gian biên dịch: trình biên dịch mong đợi hai biến nhưng chỉ nhận được một biến. Cú pháp được chọn bởi Go chỉ đơn giản return x, yvà chấp nhận các giá trị trả về mà bạn thực hiệnx,y = multiRet()
slebetman

Bạn có nghĩa là Go không có biến của loại tuple?
gnasher729

Giống như bất kỳ ngôn ngữ giống C nào khác, Go có cấu trúc và giống như hầu hết các ngôn ngữ hiện đại, Go có loại bản đồ (mảng băm / liên kết) để bạn có thể triển khai các bộ dữ liệu dưới dạng cấu trúc hoặc bản đồ. Một điều mà Go không thường thấy là các cấu trúc ẩn danh - các cấu trúc không có tên.
slebetman

Chà, những ngôn ngữ mới hơn như Swift có bộ dữ liệu. Phải tự thực hiện các bộ dữ liệu chiếm phần lớn lợi thế.
gnasher729

Trong LISP phổ biến của nó. Chúng tôi cũng có thể sử dụng liên kết nhiều giá trị để gán từng giá trị trả về cho một biến
Nói là giá rẻ Hiển thị cho tôi Mã
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.