Mengapa sebagian besar bahasa pemrograman hanya mendukung pengembalian satu nilai dari suatu fungsi? [Tutup]


118

Apakah ada alasan mengapa fungsi di sebagian besar (?) Bahasa pemrograman dirancang untuk mendukung sejumlah parameter input tetapi hanya satu nilai balik?

Dalam kebanyakan bahasa, dimungkinkan untuk "mengatasi" batasan itu, misalnya dengan menggunakan parameter-out, pointer kembali atau dengan mendefinisikan / mengembalikan struct / kelas. Tetapi tampaknya aneh bahwa bahasa pemrograman tidak dirancang untuk mendukung beberapa nilai pengembalian dengan cara yang lebih "alami".

Apakah ada penjelasan untuk ini?


40
karena Anda dapat mengembalikan array ...
nathan hayfield

6
Jadi mengapa tidak hanya mengizinkan satu argumen? Saya menduga itu terkait dengan pidato. Ambil beberapa ide, hancurkan mereka menjadi satu hal yang Anda kembalikan kepada siapa saja yang cukup beruntung / tidak beruntung untuk mendengarkan. Pengembaliannya hampir seperti sebuah opini. "Ini" adalah apa yang Anda lakukan dengan "ini."
Erik Reppen

30
Saya percaya pendekatan python ini cukup sederhana dan elegan: ketika Anda harus kembali beberapa nilai hanya kembali tupel: def f(): return (1,2,3)dan kemudian Anda dapat menggunakan tuple-membongkar untuk "split" tupel: a,b,c = f() #a=1,b=2,c=3. Tidak perlu membuat array dan mengekstrak elemen secara manual, tidak perlu mendefinisikan kelas baru.
Bakuriu

7
Saya dapat memberitahu Anda bahwa Matlab memiliki sejumlah variabel nilai pengembalian. Jumlah argumen keluaran ditentukan oleh tanda tangan panggilan (misalnya [a, b] = f()vs. [a, b, c] = f()) dan diperoleh di dalam foleh nargout. Saya bukan penggemar Matlab tapi ini sebenarnya sangat berguna di kali.
gerrit

5
Saya pikir jika sebagian besar bahasa pemrograman dirancang seperti itu masih bisa diperdebatkan. Dalam sejarah bahasa pemrograman, ada beberapa bahasa yang sangat populer dibuat seperti itu (seperti Pascal, C, C ++, Java, VB klasik), tetapi hari ini ada juga banyak bahasa lain yang mendapatkan lebih banyak dan lebih banyak penggemar yang memungkinkan beberapa pengembalian nilai-nilai.
Doc Brown

Jawaban:


58

Beberapa bahasa, seperti Python , mendukung banyak nilai pengembalian secara asli, sementara beberapa bahasa seperti C # mendukungnya melalui pustaka dasar mereka.

Tetapi secara umum, bahkan dalam bahasa yang mendukungnya, beberapa nilai pengembalian tidak sering digunakan karena mereka ceroboh:

  • Fungsi yang mengembalikan beberapa nilai sulit untuk disebutkan dengan jelas .
  • Sangat mudah untuk keliru urutan nilai pengembalian

    (password, username) = GetUsernameAndPassword()  
    

    (Untuk alasan yang sama ini, banyak orang menghindari memiliki terlalu banyak parameter untuk suatu fungsi; beberapa bahkan menganggapnya sebagai fungsi seharusnya tidak boleh memiliki dua parameter dari jenis yang sama!)

  • Bahasa OOP sudah memiliki alternatif yang lebih baik untuk beberapa nilai-kembali: kelas.
    Mereka lebih kuat-diketik, mereka menjaga nilai-nilai kembali dikelompokkan sebagai satu unit logis, dan mereka menjaga nama (properti) nilai-nilai kembali konsisten di semua penggunaan.

Satu tempat mereka berada cukup nyaman dalam bahasa (seperti Python) di mana beberapa nilai kembali dari satu fungsi dapat digunakan sebagai beberapa parameter masukan yang lain. Tapi, kasus penggunaan di mana ini adalah desain yang lebih baik daripada menggunakan kelas cukup ramping.


50
Sulit untuk mengatakan bahwa mengembalikan tuple adalah mengembalikan beberapa hal. Ini mengembalikan satu tuple . Kode yang Anda tulis hanya membongkarnya dengan bersih menggunakan gula sintaksis.

10
@Lego: Saya tidak melihat perbedaan - tuple adalah definisi beberapa nilai. Apa yang Anda anggap "nilai pengembalian berganda," jika bukan ini?
BlueRaja - Danny Pflughoeft

19
Ini perbedaan yang sangat kabur, tetapi pertimbangkan Tuple kosong (). Apakah itu satu hal atau nol hal? Secara pribadi, saya akan mengatakan satu hal. Saya dapat menetapkan dengan x = ()baik, sama seperti saya dapat menetapkan x = randomTuple(). Dalam yang terakhir jika tuple yang dikembalikan kosong atau tidak, saya masih dapat menetapkan satu tuple yang dikembalikan x.

19
... Saya tidak pernah mengklaim bahwa tupel tidak dapat digunakan untuk hal-hal lain. Tetapi berdebat bahwa "Python tidak mendukung beberapa nilai pengembalian, ia mendukung tuple" hanyalah menjadi sangat tak berguna. Ini masih jawaban yang benar.
BlueRaja - Danny Pflughoeft

14
Baik tuple atau kelas adalah "nilai ganda".
Andres F.

54

Karena fungsi adalah konstruk matematika yang melakukan perhitungan dan mengembalikan hasil. Memang, banyak yang "di bawah tudung" dari tidak sedikit bahasa pemrograman berfokus hanya pada satu input dan satu output, dengan beberapa input hanya pembungkus tipis di sekitar input - dan ketika output nilai tunggal tidak bekerja, menggunakan satu struktur kohesif (atau tuple, atau Maybe) menjadi output (meskipun nilai kembali "tunggal" terdiri dari banyak nilai).

Ini tidak berubah karena programmer telah menemukan outparameter sebagai konstruksi yang canggung yang berguna hanya dalam skenario terbatas. Seperti halnya banyak hal lain, dukungan tidak ada karena kebutuhan / permintaan tidak ada.


5
@FrustratedWithFormsDesigner - ini muncul sedikit dalam pertanyaan baru-baru ini . Saya dapat mengandalkan di satu sisi berapa kali saya ingin beberapa output dalam 20 tahun.
Telastyn

61
Fungsi dalam Matematika dan fungsi dalam kebanyakan bahasa pemrograman adalah dua binatang yang sangat berbeda.
tdammers

17
@Dammers di hari-hari awal, mereka sangat mirip dalam pemikiran. Fortran, pascal dan sejenisnya di mana sangat dipengaruhi oleh matematika lebih dari arsitektur komputasi.

9
@tdammers - bagaimana bisa begitu? Maksud saya untuk sebagian besar bahasa, intinya adalah kalkulus lambda pada akhirnya - satu input, satu output, tanpa efek samping. Yang lainnya adalah simulasi / retasan di atas itu. Fungsi pemrograman mungkin tidak murni dalam arti beberapa input dapat menghasilkan output yang sama tetapi semangat ada di sana.
Telastyn

16
@SteveEvers: sangat disayangkan bahwa nama "fungsi" mengambil alih dalam pemrograman imperatif, bukannya "prosedur" atau "rutin" yang lebih tepat. Dalam pemrograman fungsional, fungsi menyerupai fungsi matematika jauh lebih dekat.
tdammers

35

Dalam matematika, fungsi "terdefinisi dengan baik" adalah fungsi di mana hanya ada 1 output untuk input yang diberikan (sebagai catatan, Anda hanya dapat memiliki fungsi input tunggal, dan masih secara semantik mendapatkan beberapa input menggunakan currying ).

Untuk fungsi multi-nilai (mis. Akar kuadrat dari integer positif, misalnya), cukup untuk mengembalikan koleksi, atau urutan nilai.

Untuk jenis fungsi yang Anda bicarakan (mis. Fungsi yang mengembalikan banyak nilai, dari jenis yang berbeda ) Saya melihatnya sedikit berbeda dari yang Anda kira : Saya melihat kebutuhan / penggunaan params sebagai solusi untuk desain yang lebih baik atau struktur data yang lebih bermanfaat. Sebagai contoh, saya lebih suka jika *.TryParse(...)metode mengembalikan Maybe<T>monad daripada menggunakan param. Pikirkan kode ini dalam F #:

let s = "1"
match tryParse s with
| Some(i) -> // do whatever with i
| None -> // failed to parse

Dukungan Compiler / IDE / analisis sangat baik untuk konstruksi ini. Ini akan memecahkan sebagian besar "kebutuhan" untuk params. Sejujurnya, saya tidak bisa memikirkan metode lain di mana ini tidak akan menjadi solusi.

Untuk skenario lain - yang saya tidak ingat - cukup Tuple.


1
Selain itu, saya benar-benar ingin dapat menulis dalam C #: var (value, success) = ParseInt("foo");yang akan menjadi tipe kompilasi-waktu diperiksa karena (int, bool) ParseInt(string s) { }dideklarasikan. Saya tahu ini bisa dilakukan dengan obat generik, tapi tetap saja itu akan menjadi tambahan bahasa yang bagus.
Meringis putus asa

10
@GrimaceofDespair yang Anda inginkan adalah merusak sintaksis, bukan beberapa nilai pengembalian.
Domenic

2
@warren: Ya. Lihat di sini dan perhatikan bahwa solusi seperti itu tidak akan berkelanjutan: en.wikipedia.org/wiki/Well-definition
Steven Evers

4
Gagasan matematis tentang definisi yang jelas tidak ada hubungannya dengan jumlah output yang dihasilkan suatu fungsi. Ini berarti bahwa output akan selalu sama jika inputnya sama. Sebenarnya, fungsi matematika mengembalikan satu nilai, tetapi nilai itu sering berupa tuple. Bagi seorang ahli matematika, pada dasarnya tidak ada perbedaan antara ini dan mengembalikan beberapa nilai. Argumen bahwa fungsi pemrograman seharusnya hanya mengembalikan satu nilai karena itulah fungsi matematika lakukan tidak terlalu menarik.
Michael Siler

1
@MichaelSiler (Saya setuju dengan komentar Anda) Tetapi harap dicatat bahwa argumen dapat dibalik: "argumen bahwa fungsi-fungsi program dapat mengembalikan beberapa nilai karena mereka dapat mengembalikan nilai tuple tunggal juga tidak terlalu menarik" :)
Andres F.

23

Selain apa yang telah dikatakan ketika Anda melihat paradigma yang digunakan dalam perakitan ketika fungsi mengembalikannya meninggalkan pointer ke objek yang kembali dalam register tertentu. Jika mereka menggunakan register variabel / multipel, fungsi panggilan tidak akan tahu ke mana harus mendapatkan nilai yang dikembalikan jika fungsi itu ada di pustaka. Sehingga akan membuat menghubungkan ke perpustakaan menjadi sulit dan alih-alih mengatur jumlah pointer yang dapat dikembalikan, mereka pergi dengan satu. Bahasa tingkat yang lebih tinggi tidak memiliki alasan yang sama.


Ah! Titik detail yang sangat menarik. +1!
Steven Evers

Ini harus menjadi jawaban yang diterima. Biasanya orang berpikir tentang mesin target ketika mereka membuat kompiler. Analogi lain adalah mengapa kita memiliki int, float, char / string, dll. Karena itulah yang didukung oleh mesin target. Sekalipun targetnya bukan bare metal (mis. Jvm), Anda tetap ingin mendapatkan kinerja yang layak dengan tidak meniru terlalu banyak.
imel96

26
... Anda dapat dengan mudah mendefinisikan konvensi pemanggilan untuk mengembalikan beberapa nilai dari suatu fungsi, dengan cara yang hampir sama Anda dapat mendefinisikan konvensi pemanggilan untuk meneruskan beberapa nilai ke suatu fungsi. Ini bukan jawaban. -1
BlueRaja - Danny Pflughoeft

2
Akan menarik untuk mengetahui apakah mesin eksekusi berbasis stack (JVM, CLR) pernah mempertimbangkan / mengizinkan beberapa nilai pengembalian .. itu harus cukup mudah; penelepon hanya perlu memunculkan jumlah nilai yang tepat, sama seperti itu mendorong jumlah argumen yang tepat!
Lorenzo Dematté

1
@David no, cdecl memungkinkan untuk (secara teoritis) jumlah parameter yang tidak terbatas (itu sebabnya fungsi varargs dimungkinkan) . Meskipun beberapa kompiler-C dapat membatasi Anda untuk beberapa lusin atau ratusan argumen per fungsi, yang saya pikir masih lebih masuk akal -_-
BlueRaja - Danny Pflughoeft

18

Banyak kasus penggunaan di mana Anda akan menggunakan beberapa nilai pengembalian di masa lalu sama sekali tidak diperlukan lagi dengan fitur bahasa modern. Ingin mengembalikan kode kesalahan? Lempar pengecualian atau kembalikan Either<T, Throwable>. Ingin mengembalikan hasil opsional? Kembalikan Option<T>. Ingin mengembalikan salah satu dari beberapa jenis? Kembalikan Either<T1, T2>serikat pekerja atau yang ditandai.

Dan bahkan dalam kasus di mana Anda benar - benar perlu mengembalikan beberapa nilai, bahasa modern biasanya mendukung tupel atau semacam struktur data (daftar, array, kamus) atau objek serta beberapa bentuk pengikatan ikatan atau pencocokan pola, yang membuat pengemasan beberapa nilai Anda menjadi nilai tunggal dan kemudian merusaknya lagi menjadi beberapa nilai sepele.

Berikut adalah beberapa contoh bahasa yang tidak mendukung pengembalian beberapa nilai. Saya tidak benar-benar melihat bagaimana menambahkan dukungan untuk beberapa nilai pengembalian akan membuatnya secara signifikan lebih ekspresif untuk mengimbangi biaya fitur bahasa baru.

Rubi

def foo; return 1, 2, 3 end

one, two, three = foo

one
# => 1

three
# => 3

Python

def foo(): return 1, 2, 3

one, two, three = foo()

one
# >>> 1

three
# >>> 3

Scala

def foo = (1, 2, 3)

val (one, two, three) = foo
// => one:   Int = 1
// => two:   Int = 2
// => three: Int = 3

Haskell

let foo = (1, 2, 3)

let (one, two, three) = foo

one
-- > 1

three
-- > 3

Perl6

sub foo { 1, 2, 3 }

my ($one, $two, $three) = foo

$one
# > 1

$three
# > 3

1
Saya pikir satu aspek adalah bahwa, dalam beberapa bahasa (seperti Matlab), suatu fungsi bisa fleksibel dalam berapa banyak nilai yang dikembalikan; lihat komentar saya di atas . Ada banyak aspek dalam Matlab yang tidak saya sukai, tetapi ini adalah salah satu dari sedikit (mungkin satu-satunya) fitur yang saya lewatkan ketika melakukan porting dari Matlab ke eg Python.
gerrit

1
Tapi bagaimana dengan bahasa dinamis seperti Python atau Ruby? Misalkan saya menulis sesuatu seperti sortfungsi Matlab : sorted = sort(array)hanya mengembalikan array yang diurutkan, sedangkan [sorted, indices] = sort(array)mengembalikan keduanya. Satu - satunya cara saya bisa memikirkan dalam Python adalah dengan mengedarkan bendera ke sortsepanjang garis sort(array, nout=2)atau sort(array, indices=True).
gerrit

2
@ MikeCellini saya tidak berpikir begitu. Suatu fungsi dapat mengetahui dengan berapa banyak argumen keluaran fungsi dipanggil ( [a, b, c] = func(some, thing)) dan bertindak sesuai. Ini berguna, misalnya, jika menghitung argumen output pertama adalah murah tetapi menghitung yang kedua mahal. Saya tidak terbiasa dengan bahasa lain di mana setara dengan Matlabs nargouttersedia run-time.
gerrit

1
@gerrit solusi yang tepat dengan Python adalah untuk menulis ini: sorted, _ = sort(array).
Miles Rout

1
@MilesRout: Dan sortfungsinya dapat mengatakan bahwa itu tidak perlu menghitung indeks? Itu keren, saya tidak tahu itu.
Jörg W Mittag

12

Alasan sebenarnya bahwa nilai pengembalian tunggal sangat populer adalah ungkapan yang digunakan dalam banyak bahasa. Dalam bahasa apa pun di mana Anda dapat memiliki ekspresi seperti x + 1Anda sudah berpikir dalam hal nilai pengembalian tunggal karena Anda mengevaluasi ekspresi di kepala Anda dengan memecahnya menjadi beberapa bagian dan memutuskan nilai masing-masing bagian. Anda melihat xdan memutuskan bahwa nilainya adalah 3 (misalnya), dan Anda melihat 1 lalu Anda melihatnyax + 1dan menyatukan semuanya untuk memutuskan bahwa nilai keseluruhan adalah 4. Setiap bagian sintaksis dari ekspresi memiliki satu nilai, bukan jumlah nilai lainnya; itu adalah semantik alami ekspresi yang diharapkan setiap orang. Bahkan ketika suatu fungsi mengembalikan sepasang nilai, itu tetap benar-benar mengembalikan satu nilai yang melakukan pekerjaan dua nilai, karena gagasan fungsi yang mengembalikan dua nilai yang entah bagaimana tidak terbungkus dalam satu koleksi terlalu aneh.

Orang tidak ingin berurusan dengan semantik alternatif yang akan diperlukan untuk memiliki fungsi mengembalikan lebih dari satu nilai. Misalnya, dalam bahasa berbasis stack seperti Forth, Anda dapat memiliki sejumlah nilai pengembalian karena setiap fungsi cukup memodifikasi bagian atas tumpukan, membuka input dan mendorong output sesuka hati. Itu sebabnya Forth tidak memiliki jenis ekspresi yang dimiliki bahasa normal.

Perl adalah bahasa lain yang terkadang dapat bertindak seperti fungsi yang mengembalikan banyak nilai, meskipun biasanya hanya dianggap mengembalikan daftar. Cara daftar "interpolasi" di Perl memberi kita daftar seperti (1, foo(), 3)yang mungkin memiliki 3 elemen seperti kebanyakan orang yang tidak tahu Perl harapkan, tetapi bisa dengan mudah hanya memiliki 2 elemen, 4 elemen, atau jumlah elemen yang lebih besar tergantung pada foo(). Daftar dalam Perl diratakan sehingga daftar sintaksis tidak selalu memiliki semantik daftar; itu bisa hanya sepotong daftar yang lebih besar.

Cara lain untuk memiliki fungsi mengembalikan beberapa nilai adalah dengan memiliki semantik ekspresi alternatif di mana setiap ekspresi dapat memiliki beberapa nilai dan setiap nilai mewakili suatu kemungkinan. Ambillah x + 1lagi, tetapi kali ini bayangkan yang xmemiliki dua nilai {3, 4}, maka nilai x + 1akan menjadi {4, 5}, dan nilai-nilai x + xakan menjadi {6, 8}, atau mungkin {6, 7, 8} , tergantung pada apakah satu evaluasi diizinkan untuk menggunakan beberapa nilai untuk x. Bahasa seperti itu mungkin diimplementasikan menggunakan backtracking seperti Prolog menggunakan untuk memberikan beberapa jawaban untuk suatu query.

Singkatnya, panggilan fungsi adalah unit sintaksis tunggal dan unit sintaksis tunggal memiliki nilai tunggal dalam semantik ekspresi yang kita semua tahu dan sukai. Semantik lainnya akan memaksa Anda melakukan cara aneh dalam melakukan sesuatu, seperti Perl, Prolog, atau Forth.


9

Seperti yang disarankan dalam jawaban ini , ini adalah masalah dukungan perangkat keras, meskipun tradisi dalam desain bahasa juga berperan.

ketika suatu fungsi mengembalikannya, meninggalkan sebuah pointer ke objek yang kembali dalam register tertentu

Dari tiga bahasa pertama, Fortran, Lisp dan COBOL, yang pertama menggunakan nilai pengembalian tunggal sebagaimana dimodelkan pada matematika. Yang kedua mengembalikan sejumlah parameter sewenang-wenang dengan cara yang sama seperti menerimanya: sebagai sebuah daftar (bisa juga diperdebatkan bahwa parameter hanya lulus dan mengembalikan satu parameter: alamat daftar). Yang ketiga mengembalikan nol atau satu nilai.

Bahasa-bahasa pertama ini sangat memengaruhi desain bahasa yang mengikutinya, meskipun satu-satunya yang mengembalikan banyak nilai, Lisp, tidak pernah mengumpulkan banyak popularitas.

Ketika C datang, walaupun dipengaruhi oleh bahasa sebelumnya, C memberikan fokus yang besar pada penggunaan sumber daya perangkat keras yang efisien, menjaga hubungan erat antara apa yang dilakukan oleh bahasa C dan kode mesin yang mengimplementasikannya. Beberapa fitur tertua, seperti variabel "auto" vs "register", adalah hasil dari filosofi desain itu.

Harus juga ditunjukkan bahwa bahasa assembly secara luas populer hingga tahun 80-an, ketika akhirnya mulai dihapus dari pengembangan arus utama. Orang-orang yang menulis kompiler dan membuat bahasa terbiasa dengan perakitan, dan, sebagian besar, tetap menggunakan yang terbaik di sana.

Sebagian besar bahasa yang menyimpang dari norma ini tidak pernah menemukan banyak popularitas, dan, oleh karena itu, tidak pernah memainkan peran yang kuat mempengaruhi keputusan para perancang bahasa (yang, tentu saja, terinspirasi oleh apa yang mereka ketahui).

Jadi mari kita periksa bahasa assembly. Mari kita lihat pertama pada 6502 , mikroprosesor 1975 yang terkenal digunakan oleh mikrokomputer Apple II dan VIC-20. Itu sangat lemah dibandingkan dengan apa yang digunakan dalam mainframe dan minicomputer pada waktu itu, meskipun kuat dibandingkan dengan komputer pertama 20, 30 tahun sebelumnya, pada awal bahasa pemrograman.

Jika Anda melihat deskripsi teknis, ia memiliki 5 register plus beberapa flag satu-bit. Satu-satunya register "penuh" adalah Program Counter (PC) - yang mendaftar menunjuk ke instruksi selanjutnya yang akan dieksekusi. Register lain di mana akumulator (A), dua register "indeks" (X dan Y), dan stack pointer (SP).

Memanggil subrutin menempatkan PC dalam memori yang ditunjuk oleh SP, dan kemudian menurunkan SP. Kembali dari subrutin bekerja secara terbalik. Seseorang dapat mendorong dan menarik nilai-nilai lain pada stack, tetapi sulit untuk merujuk ke memori relatif terhadap SP, sehingga menulis kembali subrutin peserta sulit. Hal yang kita anggap remeh, menyebut subrutin kapan saja kita merasa, tidak begitu umum pada arsitektur ini. Seringkali, "tumpukan" yang terpisah akan dibuat sehingga parameter dan alamat kembali subrutin akan tetap terpisah.

Jika Anda melihat prosesor yang menginspirasi 6502, 6800 , itu memiliki register tambahan, Register Index (IX), selebar SP, yang bisa menerima nilai dari SP.

Pada mesin, memanggil subrutin masuk kembali terdiri dari mendorong parameter pada stack, mendorong PC, mengubah PC ke alamat baru, dan kemudian subrutin akan mendorong variabel lokalnya pada stack . Karena jumlah variabel dan parameter lokal diketahui, mengatasinya dapat dilakukan relatif terhadap tumpukan. Misalnya, fungsi yang menerima dua parameter dan memiliki dua variabel lokal akan terlihat seperti ini:

SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1

Itu bisa disebut berapa kali karena semua ruang sementara ada di stack.

The 8080 , digunakan pada TRS-80 dan sejumlah mikrokomputer berbasis CP / M dapat melakukan sesuatu yang mirip dengan 6800, dengan mendorong SP pada stack dan kemudian muncul pada register tidak langsungnya, HL.

Ini adalah cara yang sangat umum untuk mengimplementasikan berbagai hal, dan bahkan mendapat dukungan lebih banyak pada prosesor yang lebih modern, dengan Base Pointer yang membuat membuang semua variabel lokal sebelum kembali dengan mudah.

Masalahnya, adalah, bagaimana Anda mengembalikan sesuatu ? Register prosesor tidak terlalu banyak sejak awal, dan sering kali diperlukan beberapa dari mereka bahkan untuk mencari tahu bagian memori yang harus diatasi. Mengembalikan barang-barang di tumpukan akan menjadi rumit: Anda harus menghapus semuanya, menyimpan PC, mendorong parameter pengembalian (yang akan disimpan di mana saja?), Lalu mendorong PC lagi dan kembali.

Jadi yang biasanya dilakukan adalah memesan satu register untuk nilai pengembalian. Kode panggilan tahu nilai pengembalian akan berada di register tertentu, yang harus dipertahankan sampai dapat disimpan atau digunakan.

Mari kita lihat bahasa yang memungkinkan beberapa nilai pengembalian: Keempat. Apa yang dilakukan Forth adalah menjaga stack kembali terpisah (RP) dan tumpukan data (SP), sehingga semua fungsi harus lakukan adalah pop semua parameternya dan meninggalkan nilai kembali pada stack. Karena tumpukan kembali terpisah, itu tidak menghalangi.

Sebagai seseorang yang belajar bahasa assembly dan Forth dalam enam bulan pertama pengalaman dengan komputer, beberapa nilai pengembalian terlihat sepenuhnya normal bagi saya. Operator seperti Forth's /mod, yang mengembalikan divisi integer dan yang lainnya, tampak jelas. Di sisi lain, saya dapat dengan mudah melihat bagaimana seseorang yang pengalaman awalnya adalah C merasa konsep itu aneh: itu bertentangan dengan harapan mereka yang mendarah daging tentang apa "fungsi" itu.

Sedangkan untuk matematika ... well, saya memprogram komputer jauh sebelum saya bisa berfungsi di kelas matematika. Ada adalah seluruh bagian dari CS dan bahasa pemrograman yang dipengaruhi oleh matematika, tapi, sekali lagi, ada seluruh bagian yang tidak.

Jadi kami memiliki pertemuan faktor-faktor di mana matematika mempengaruhi desain bahasa awal, di mana kendala perangkat keras menentukan apa yang mudah diimplementasikan, dan di mana bahasa populer mempengaruhi bagaimana perangkat keras berevolusi (mesin Lisp dan prosesor mesin Forth adalah roadkill dalam proses ini).


@gnat Penggunaan "untuk menginformasikan" seperti pada "untuk memberikan kualitas esensial" disengaja.
Daniel C. Sobral

jangan ragu untuk mengembalikan jika Anda merasa sangat tentang ini; per pengaruh membaca saya cocok sedikit lebih baik di sini: "mempengaruhi ... dalam cara yang penting"
agas

1
+1 Seperti yang ditunjukkan, bahwa penghitungan register jarang dari CPU awal dibandingkan dengan kumpulan register CPU kontemporer yang melimpah (juga digunakan di banyak ABI, misalnya x64 abi) mungkin merupakan pengubah permainan dan alasan yang memaksa sebelumnya untuk hanya mengembalikan 1 nilai mungkin saat ini hanya menjadi alasan historis.
BitTickler

Saya tidak yakin bahwa CPU mikro 8-bit awal memiliki banyak pengaruh pada desain bahasa dan hal-hal apa yang dianggap diperlukan dalam konvensi pemanggilan C atau Fortran di semua arsitektur. Fortran mengasumsikan Anda dapat melewatkan array args (dasarnya pointer). Jadi, Anda sudah memiliki masalah implementasi utama untuk Fortran normal pada mesin seperti 6502, karena kurangnya pengalamatan-mode untuk pointer + index, seperti dibahas dalam jawaban Anda dan di Mengapa kompiler C hingga Z80 menghasilkan kode yang buruk? pada retrocomputing.SE.
Peter Cordes

Fortran, seperti C, juga mengasumsikan Anda dapat melewati sejumlah argumen yang sewenang-wenang, dan memiliki akses acak ke mereka dan jumlah penduduk setempat yang sewenang-wenang, bukan? Seperti yang baru saja Anda jelaskan, Anda tidak dapat melakukan itu dengan mudah pada 6502 karena menangani stack-relatif bukanlah suatu hal, kecuali Anda meninggalkan reentrancy. Anda dapat memasukkannya ke penyimpanan statis. Jika Anda dapat melewati daftar argumen arbitrer, Anda dapat menambahkan parameter tersembunyi tambahan untuk nilai pengembalian (mis. Di luar yang pertama) yang tidak sesuai dengan register.
Peter Cordes

7

Bahasa fungsional yang saya tahu dapat mengembalikan banyak nilai dengan mudah melalui penggunaan tupel (dalam bahasa yang diketik secara dinamis, Anda bahkan dapat menggunakan daftar). Tuples juga didukung dalam bahasa lain:

f :: Int -> (Int, Int)
f x = (x - 1, x + 1)

// Even C++ have tuples - see Boost.Graph for use
std::pair<int, int> f(int x) {
  return std::make_pair(x - 1, x + 1);
}

Dalam contoh di atas, fadalah fungsi yang mengembalikan 2 int.

Demikian pula, ML, Haskell, F #, dll., Juga dapat mengembalikan struktur data (pointer terlalu rendah untuk kebanyakan bahasa). Saya belum pernah mendengar bahasa GP modern dengan batasan seperti itu:

data MyValue = MyValue Int Int

g :: Int -> MyValue
g x = MyValue (x - 1, x + 1)

Akhirnya, outparameter dapat ditiru bahkan dalam bahasa fungsional oleh IORef. Ada beberapa alasan mengapa tidak ada dukungan asli untuk variabel keluar di sebagian besar bahasa:

  • Semantik tidak jelas : Apakah fungsi berikut mencetak 0, atau 1? Saya tahu bahasa yang akan mencetak 0, dan yang akan mencetak 1. Ada manfaat untuk keduanya (baik dalam hal kinerja, serta mencocokkan model mental programmer):

    int x;
    
    int f(out int y) {
      x = 0;
      y = 1;
      printf("%d\n", x);
    }
    f(out x);
    
  • Efek non-lokal : Seperti pada contoh di atas, Anda dapat menemukan bahwa Anda dapat memiliki rantai panjang dan fungsi terdalam memengaruhi kondisi global. Secara umum, itu membuat lebih sulit untuk berpikir tentang apa persyaratan fungsi, dan jika perubahan itu sah. Mengingat sebagian besar paradigma modern mencoba melokalisasi efek (enkapsulasi dalam OOP) atau menghilangkan efek samping (pemrograman fungsional), ia bertentangan dengan paradigma tersebut.

  • Menjadi berlebihan : Jika Anda memiliki tupel, Anda memiliki 99% fungsionalitas outparameter dan 100% penggunaan idiomatik. Jika Anda menambahkan pointer ke dalam campuran Anda menutupi sisa 1%.

Saya mengalami kesulitan menyebutkan satu bahasa yang tidak dapat mengembalikan beberapa nilai dengan menggunakan tuple, kelas atau outparameter (dan dalam kebanyakan kasus 2 atau lebih dari metode tersebut diperbolehkan).


+1 Untuk menyebutkan bagaimana bahasa fungsional menangani ini dengan cara yang elegan & tidak menyakitkan.
Andres F.

1
Secara teknis Anda masih mengembalikan nilai tunggal: D (Hanya saja nilai tunggal ini sepele untuk diuraikan menjadi beberapa nilai).
Thomas Eding

1
Saya akan mengatakan bahwa parameter dengan semantik "keluar" yang sebenarnya harus berperilaku sebagai kompiler sementara yang akan disalin ke tujuan ketika suatu metode keluar secara normal; satu dengan variabel semantik "inout" harus berperilaku sebagai kompiler sementara yang diambil dari variabel yang dilewati saat masuk dan ditulis kembali saat keluar; satu dengan semantik "ref" harus berperilaku sebagai alias. Parameter yang disebut "keluar" dari C # adalah benar-benar parameter "ref", dan berperilaku seperti itu.
supercat

1
Tuple "solusi" juga tidak datang secara gratis. Ini memblokir peluang optimasi. Jika ABI ada yang memungkinkan nilai kembali N dikembalikan dalam register CPU, kompilator sebenarnya bisa mengoptimalkan, daripada membuat contoh tuple dan membangunnya.
BitTickler

1
@ BitTickler tidak ada yang mencegah pengembalian pertama n bidang struct yang harus dilewati oleh register jika Anda mengontrol ABI.
Maciej Piechotka

6

Saya pikir itu karena ekspresi , seperti (a + b[i]) * c.

Ekspresi terdiri dari nilai-nilai "tunggal". Fungsi yang mengembalikan nilai singular dapat langsung digunakan dalam ekspresi, menggantikan salah satu dari empat variabel yang ditunjukkan di atas. Fungsi multi-output setidaknya agak canggung dalam ekspresi.

Saya pribadi merasa bahwa ini adalah yang hal yang khusus tentang nilai kembali tunggal. Anda bisa mengatasinya dengan menambahkan sintaks untuk menentukan mana dari beberapa nilai kembali yang ingin Anda gunakan dalam ekspresi, tapi itu pasti lebih canggung daripada notasi matematika lama yang bagus, yang ringkas dan akrab bagi semua orang.


4

Itu sedikit menyulitkan sintaks, tetapi tidak ada alasan yang baik di tingkat implementasi untuk tidak mengizinkannya. Bertentangan dengan beberapa respons lain, mengembalikan beberapa nilai, jika tersedia, mengarah pada kode yang lebih jelas dan lebih efisien. Saya tidak dapat menghitung seberapa sering saya berharap dapat mengembalikan nilai X dan Y, atau boolean "sukses" dan nilai yang berguna.


3
Bisakah Anda memberikan contoh di mana beberapa pengembalian memberikan kode yang lebih jelas dan / atau lebih efisien?
Steven Evers

3
Misalnya, dalam pemrograman C ++ COM, banyak fungsi memiliki satu [out]parameter, tetapi hampir semua mengembalikan HRESULT(kode kesalahan). Akan sangat praktis untuk mendapatkan pasangan di sana. Dalam bahasa yang memiliki dukungan yang baik untuk tupel, seperti Python, ini akan digunakan dalam banyak kode yang saya lihat.
Felix Dombek

Dalam beberapa bahasa, Anda akan mengembalikan vektor dengan koordinat X dan Y, dan mengembalikan nilai berguna apa pun akan dianggap sebagai "sukses" dengan pengecualian, mungkin membawa nilai berguna itu, digunakan untuk kegagalan.
doppelgreener

3
Banyak kali Anda akhirnya menyandikan informasi menjadi nilai pengembalian dengan cara yang tidak jelas - yaitu; nilai negatif adalah kode kesalahan, nilai positif adalah hasil. Yuk. Mengakses tabel hash, selalu berantakan untuk menunjukkan apakah item ditemukan dan juga mengembalikan item.
ddyer

@SteveEvers The Matlab sortberfungsi normal macam array: sorted_array = sort(array). Kadang-kadang saya juga perlu yang sesuai indeks: [sorted_array, indices] = sort(array). Kadang-kadang saya hanya menginginkan indeks: [~, indices]= sort (array) . The function sort` sebenarnya dapat mengetahui berapa banyak argumen output yang diperlukan, jadi jika pekerjaan tambahan diperlukan untuk 2 output dibandingkan dengan 1, ia dapat menghitung output tersebut hanya jika diperlukan.
gerrit

2

Dalam sebagian besar bahasa di mana fungsi didukung, Anda dapat menggunakan panggilan fungsi di mana saja variabel yang dapat digunakan: -

x = n + sqrt(y);

Jika fungsi mengembalikan lebih dari satu nilai, ini tidak akan berfungsi. Bahasa yang diketik secara dinamis seperti python akan memungkinkan Anda untuk melakukan ini, tetapi, dalam kebanyakan kasus itu akan memunculkan kesalahan run time kecuali jika itu bisa menyelesaikan sesuatu yang masuk akal untuk dilakukan dengan tuple di tengah persamaan.


5
Hanya saja, jangan gunakan fungsi yang tidak pantas. Ini tidak berbeda dengan "masalah" yang ditimbulkan oleh fungsi yang tidak mengembalikan nilai, atau mengembalikan nilai non-numerik.
ddyer

3
Dalam bahasa yang saya gunakan yang menawarkan beberapa nilai pengembalian (SciLab, misalnya), nilai pengembalian pertama adalah hak istimewa, dan akan digunakan dalam kasus di mana hanya satu nilai yang diperlukan. Jadi tidak ada masalah di sana.
The Photon

Dan bahkan ketika mereka tidak, seperti dengan tuple Python membongkar, Anda dapat memilih mana yang Anda inginkan:foo()[0]
Izkata

Tepatnya, jika suatu fungsi mengembalikan 2 nilai, maka tipe pengembaliannya adalah 2 nilai, bukan nilai tunggal. Bahasa pemrograman seharusnya tidak membaca pikiran Anda.
Mark E. Haase

1

Saya hanya ingin membangun berdasarkan jawaban Harvey. Saya awalnya menemukan pertanyaan ini di situs teknologi berita (arstechnica) dan menemukan penjelasan yang menakjubkan bahwa saya merasa benar-benar menjawab inti dari pertanyaan ini dan kurang dari semua jawaban lain (kecuali Harvey):

Asal tunggal kembali dari fungsi terletak pada kode mesin. Pada level kode mesin, suatu fungsi dapat mengembalikan nilai dalam register A (akumulator). Nilai pengembalian lainnya akan berada di tumpukan.

Bahasa yang mendukung dua nilai kembali akan mengkompilasinya sebagai kode mesin yang mengembalikan satu, dan menempatkan yang kedua di tumpukan. Dengan kata lain, nilai pengembalian kedua akan berakhir sebagai parameter keluar.

Itu seperti bertanya mengapa tugas adalah satu variabel pada suatu waktu. Anda dapat memiliki bahasa yang memungkinkan a, b = 1, 2 misalnya. Tapi itu akan berakhir pada level kode mesin menjadi a = 1 diikuti oleh b = 2.

Ada beberapa alasan dalam membuat konstruksi bahasa pemrograman memiliki kemiripan dengan apa yang sebenarnya akan terjadi ketika kode dikompilasi dan dijalankan.


Jika bahasa tingkat rendah seperti C mendukung beberapa nilai pengembalian sebagai fitur kelas satu, konvensi pemanggilan C akan mencakup beberapa register nilai kembali, cara yang sama dengan hingga 6 register digunakan untuk melewati argumen fungsi integer / pointer di x86- 64 Sistem V ABI. (Sebenarnya x86-64 SysV mengembalikan struct hingga 16 byte yang dimasukkan ke dalam pasangan register RDX: RAX. Ini bagus jika struct baru saja dimuat dan akan disimpan, tetapi biaya pembongkaran ekstra vs memiliki anggota yang terpisah dalam reg yang terpisah bahkan jika lebih sempit dari 64 bit.)
Peter Cordes

Konvensi yang jelas adalah RAX, kemudian reg-passing arg. (RDI, RSI, RDX, RCX, R8, R9). Atau di konvensi Windows x64, RCX, RDX, R8, R9. Tetapi karena C tidak memiliki beberapa nilai pengembalian, konvensi C ABI / pemanggilan hanya menetapkan beberapa register pengembalian untuk integer lebar dan beberapa struct. Lihat Standar Panggilan Prosedur untuk Arsitektur ARM®: 2 nilai pengembalian yang terpisah, tetapi terkait sebagai contoh penggunaan int lebar untuk membuat kompiler membuat asm efisien untuk menerima 2 nilai balik pada ARM.
Peter Cordes

-1

Itu dimulai dengan matematika. FORTRAN, dinamai "Formula Terjemahan" adalah kompiler pertama. FORTRAN adalah dan berorientasi pada fisika / matematika / teknik.

COBOL, hampir setua itu, tidak memiliki nilai pengembalian eksplisit; Itu hampir tidak memiliki subrutin. Sejak itu sebagian besar inersia.

Go , misalnya, memiliki beberapa nilai pengembalian, dan hasilnya lebih bersih dan lebih tidak ambigu daripada menggunakan parameter "keluar". Setelah sedikit digunakan, sangat alami dan efisien. Saya merekomendasikan beberapa nilai pengembalian dipertimbangkan untuk semua bahasa baru. Mungkin untuk bahasa-bahasa lama juga.


4
ini tidak menjawab pertanyaan yang diajukan
nyamuk

@gnat bagi saya itu menjawab. OTOH kita harus memiliki latar belakang untuk memahaminya, dan orang itu kemungkinan tidak akan mengajukan pertanyaan ...
Netch

@Netch seseorang hampir tidak membutuhkan banyak latar belakang untuk mengetahui bahwa pernyataan seperti "FORTRAN ... adalah kompiler pertama" adalah kekacauan total. Bahkan tidak salah. Sama seperti sisa dari "jawaban" ini
nyamuk

link mengatakan ada upaya sebelumnya pada kompiler, tetapi bahwa "Tim FORTRAN yang dipimpin oleh John Backus di IBM pada umumnya dikreditkan karena telah memperkenalkan kompiler lengkap pertama pada tahun 1957". Pertanyaan yang diajukan adalah mengapa hanya satu? Seperti yang saya katakan. Itu sebagian besar matematika dan inersia. Definisi matematika dari istilah "Fungsi" membutuhkan tepat satu nilai hasil. Jadi itu bentuk yang akrab.
RickyS

-2

Ini mungkin lebih berkaitan dengan warisan bagaimana panggilan fungsi dibuat dalam instruksi mesin prosesor dan fakta bahwa semua bahasa pemrograman berasal dari kode mesin: misalnya, C -> Assembly -> Machine.

Bagaimana Prosesor Melakukan Panggilan Fungsi

Program pertama ditulis dalam kode mesin dan kemudian perakitan. Prosesor mendukung pemanggilan fungsi dengan mendorong salinan semua register saat ini ke stack. Kembali dari fungsi akan memunculkan set register yang tersimpan dari tumpukan. Biasanya satu register dibiarkan tak tersentuh untuk memungkinkan fungsi kembali mengembalikan nilai.

Sekarang, mengapa prosesor dirancang dengan cara ini ... itu mungkin masalah keterbatasan sumber daya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.