Mengapa bahasa mainstream yang diketik secara statis tidak mendukung kelebihan fungsi / metode dengan jenis kembali? Saya tidak bisa memikirkan yang bisa melakukannya. Tampaknya tidak kurang bermanfaat atau masuk akal daripada mendukung kelebihan berdasarkan tipe parameter. Kenapa itu jauh kurang populer?
252
Jawaban:
Bertentangan dengan apa yang orang lain katakan, overloading oleh jenis kembali adalah mungkin dan ini dilakukan oleh beberapa bahasa modern. Keberatan yang biasa adalah bahwa dalam kode suka
Anda tidak tahu mana
func()
yang dipanggil. Ini dapat diatasi dengan beberapa cara:int main() { (string)func(); }
.Dua dari bahasa yang saya gunakan ( ab ) secara teratur menggunakan tipe overload: Perl dan Haskell . Biarkan saya jelaskan apa yang mereka lakukan.
Dalam Perl , ada perbedaan mendasar antara skalar dan konteks daftar (dan yang lainnya, tapi kami akan berpura-pura ada dua). Setiap fungsi bawaan di Perl dapat melakukan hal-hal yang berbeda tergantung pada konteksnya . Misalnya,
join
operator memaksa konteks daftar (pada hal yang sedang digabungkan) sementarascalar
operator memaksa konteks skalar, jadi bandingkan:Setiap operator di Perl melakukan sesuatu dalam konteks skalar dan sesuatu dalam konteks daftar, dan mereka mungkin berbeda, seperti yang diilustrasikan. (Ini bukan hanya untuk operator acak seperti
localtime
. Jika Anda menggunakan array@a
dalam konteks daftar, itu akan mengembalikan array, sedangkan dalam konteks skalar, ia mengembalikan jumlah elemen. Jadi misalnyaprint @a
mencetak elemen, sambilprint 0+@a
mencetak ukuran. ) Selanjutnya, setiap operator dapat memaksakan suatu konteks, misalnya penambahan+
kekuatan konteks skalar. Setiap entri dalamman perlfunc
dokumen ini. Misalnya, berikut adalah bagian dari entri untukglob EXPR
:Sekarang, apa hubungan antara daftar dan konteks skalar? Baiklah,
man perlfunc
katajadi itu bukan masalah sederhana memiliki fungsi tunggal, dan kemudian Anda melakukan konversi sederhana pada akhirnya. Sebenarnya, saya memilih
localtime
contoh karena alasan itu.Bukan hanya built-in yang memiliki perilaku ini. Setiap pengguna dapat mendefinisikan fungsi tersebut menggunakan
wantarray
, yang memungkinkan Anda untuk membedakan antara daftar, skalar, dan konteks batal. Jadi, misalnya, Anda dapat memutuskan untuk tidak melakukan apa pun jika Anda dipanggil dalam konteks kosong.Sekarang, Anda mungkin mengeluh bahwa ini bukan kelebihan yang sebenarnya dengan nilai pengembalian karena Anda hanya memiliki satu fungsi, yang diberi tahu konteks tempat ia dipanggil dan kemudian bertindak berdasarkan informasi itu. Namun, ini jelas setara (dan analog dengan bagaimana Perl tidak mengizinkan overloading biasa secara harfiah, tetapi suatu fungsi hanya dapat memeriksa argumennya). Selain itu, ini dengan baik menyelesaikan situasi ambigu yang disebutkan di awal respons ini. Perl tidak mengeluh bahwa ia tidak tahu metode panggilan mana; itu hanya menyebutnya. Yang harus dilakukan adalah mencari tahu konteks apa fungsi dipanggil, yang selalu mungkin:
(Catatan: Saya kadang-kadang mengatakan operator Perl ketika maksud saya berfungsi. Ini tidak penting untuk diskusi ini.)
Haskell mengambil pendekatan lain, yaitu tidak memiliki efek samping. Ini juga memiliki sistem tipe yang kuat, sehingga Anda dapat menulis kode seperti berikut:
Kode ini membaca angka floating point dari input standar, dan mencetak root kuadratnya. Tapi apa yang mengejutkan tentang ini? Nah, jenisnya
readLn
adalahreadLn :: Read a => IO a
. Ini artinya bahwa untuk semua jenis yang dapatRead
(secara formal, setiap jenis yang merupakan turunan dariRead
kelas jenis),readLn
dapat membacanya. Bagaimana Haskell tahu bahwa saya ingin membaca angka floating point? Ya, tipesqrt
issqrt :: Floating a => a -> a
, yang pada dasarnya berartisqrt
hanya dapat menerima angka floating point sebagai input, dan Haskell menyimpulkan apa yang saya inginkan.Apa yang terjadi ketika Haskell tidak dapat menyimpulkan apa yang saya inginkan? Nah, ada beberapa kemungkinan. Jika saya tidak menggunakan nilai kembali sama sekali, Haskell tidak akan memanggil fungsi di tempat pertama. Namun, jika saya lakukan menggunakan nilai kembali, maka Haskell akan mengeluh bahwa ia tidak dapat menyimpulkan jenis:
Saya dapat mengatasi ambiguitas dengan menentukan jenis yang saya inginkan:
Bagaimanapun, apa arti seluruh diskusi ini adalah bahwa kelebihan dengan nilai pengembalian adalah mungkin dan dilakukan, yang menjawab bagian dari pertanyaan Anda.
Bagian lain dari pertanyaan Anda adalah mengapa lebih banyak bahasa tidak melakukannya. Saya akan membiarkan orang lain menjawab itu. Namun, beberapa komentar: alasan prinsipnya mungkin adalah bahwa kesempatan untuk kebingungan benar-benar lebih besar di sini daripada kelebihan beban berdasarkan tipe argumen. Anda juga dapat melihat alasan dari masing-masing bahasa:
Ada : "Mungkin terlihat bahwa aturan resolusi kelebihan beban paling sederhana adalah menggunakan segala sesuatu - semua informasi dari konteks seluas mungkin - untuk menyelesaikan referensi kelebihan beban. Aturan ini mungkin sederhana, tetapi tidak membantu. Ini membutuhkan pembaca manusia untuk memindai teks berukuran besar secara sewenang-wenang, dan untuk membuat kesimpulan kompleks yang sewenang-wenang (seperti (g) di atas). Kami percaya bahwa aturan yang lebih baik adalah aturan yang membuat eksplisit tugas yang harus dilakukan oleh pembaca manusia atau kompiler, dan yang membuat tugas ini sealami mungkin bagi pembaca manusia. "
C ++ (subbab 7.4.1 dari Bjarne Stroustrup "The C ++ Programming Language"): "Jenis kembali tidak dianggap dalam resolusi kelebihan beban. Alasannya adalah untuk menjaga resolusi untuk operator individu atau fungsi panggilan konteks-independen. Pertimbangkan:
Jika jenis pengembalian diperhitungkan, tidak mungkin lagi melihat panggilan
sqrt()
secara terpisah dan menentukan fungsi mana yang dipanggil. "(Perhatikan, sebagai perbandingan, bahwa di Haskell tidak ada konversi implisit .)Java ( Spesifikasi Bahasa Jawa 9.4.1 ): "Salah satu metode yang diwariskan harus dapat diganti-ketik-kembali untuk setiap metode warisan lainnya, atau kesalahan kompilasi waktu terjadi." (Ya, saya tahu ini tidak memberikan alasan. Saya yakin alasannya diberikan oleh Gosling dalam "Bahasa Pemrograman Java". Mungkin seseorang memiliki salinannya? Saya yakin itu adalah "prinsip kejutan paling tidak" pada dasarnya. ) Namun, fakta menarik tentang Java: JVM memungkinkan kelebihan dengan nilai kembali! Ini digunakan, misalnya, dalam Scala , dan dapat diakses langsung melalui Java juga dengan bermain-main dengan internal.
PS. Sebagai catatan akhir, sebenarnya dimungkinkan untuk membebani dengan mengembalikan nilai dalam C ++ dengan sebuah trik. Saksi:
sumber
Foo
danBar
dukung konversi dua arah, dan metode menggunakan tipe secaraFoo
internal tetapi tipe pengembalianBar
. Jika metode seperti ini dipanggil oleh kode yang akan segera memaksa hasil untuk mengetikFoo
, menggunakanBar
tipe pengembalian mungkin berhasil, tetapi yangFoo
akan lebih baik. BTW, saya juga ingin melihat cara di mana ...var someVar = someMethod();
(atau yang lain menyatakan bahwa pengembaliannya tidak boleh digunakan dengan cara seperti itu). Misalnya, keluarga jenis yang mengimplementasikan antarmuka Fluent mungkin mendapat manfaat dari memiliki versi yang bisa berubah dan tidak dapat diubah, sehinggavar thing2 = thing1.WithX(3).WithY(5).WithZ(9);
akanWithX(3)
menyalinthing1
ke objek yang bisa berubah, bermutasi X, dan mengembalikan objek yang bisa diubah;WithY(5)
akan bermutasi Y dan mengembalikan objek yang sama; demikian juga `WithZ (9). Kemudian tugas akan dikonversi ke tipe yang tidak dapat diubah.Jika fungsi kelebihan beban oleh tipe kembali dan Anda memiliki dua kelebihan ini
tidak ada cara kompiler bisa mengetahui mana dari dua fungsi untuk memanggil setelah melihat panggilan seperti ini
Karena alasan ini, perancang bahasa sering tidak membolehkan kelebihan nilai kembali.
Beberapa bahasa (seperti MSIL), namun, jangan biarkan overloading oleh jenis kembali. Mereka juga menghadapi kesulitan di atas tentu saja, tetapi mereka memiliki solusi, yang Anda harus berkonsultasi dengan dokumentasi mereka.
sumber
Dalam bahasa seperti itu, bagaimana Anda menyelesaikan yang berikut:
jika
f
memiliki kelebihanvoid f(int)
danvoid f(string)
dang
kelebihanint g(int)
danstring g(int)
? Anda akan membutuhkan semacam disambiguator.Saya pikir situasi di mana Anda mungkin membutuhkan ini akan lebih baik dilayani dengan memilih nama baru untuk fungsi tersebut.
sumber
Untuk mencuri jawaban khusus C ++ dari pertanyaan lain yang sangat mirip (dupe?):
Jenis fungsi pengembalian tidak ikut bermain dalam resolusi kelebihan hanya karena Stroustrup (saya berasumsi dengan input dari arsitek C ++ lainnya) ingin resolusi kelebihan beban menjadi 'konteks independen'. Lihat 7.4.1 - "Jenis Kelebihan Beban dan Pengembalian" dari "Bahasa Pemrograman C ++, Edisi Ketiga".
Mereka ingin itu hanya didasarkan pada bagaimana kelebihan beban itu disebut - bukan bagaimana hasilnya digunakan (jika itu digunakan sama sekali). Memang, banyak fungsi dipanggil tanpa menggunakan hasil atau hasilnya akan digunakan sebagai bagian dari ekspresi yang lebih besar. Salah satu faktor yang saya yakin ikut bermain ketika mereka memutuskan ini adalah bahwa jika tipe kembali adalah bagian dari resolusi akan ada banyak panggilan ke fungsi kelebihan beban yang perlu diselesaikan dengan aturan yang kompleks atau harus memiliki kompilasi lemparan kesalahan bahwa panggilan tidak jelas.
Dan, Tuhan tahu, resolusi kelebihan C ++ cukup kompleks karena berdiri ...
sumber
Di haskell dimungkinkan meskipun tidak memiliki fungsi yang berlebihan. Haskell menggunakan kelas tipe. Dalam sebuah program Anda bisa melihat:
Fungsi overloading itu sendiri tidak begitu populer. Sebagian besar bahasa yang saya lihat dengan itu adalah C ++, mungkin java dan / atau C #. Dalam semua bahasa dinamis, ini adalah singkatan untuk:
Karena itu tidak ada gunanya. Kebanyakan orang tidak tertarik apakah bahasa dapat membantu Anda menjatuhkan satu baris per tempat di mana Anda menggunakannya.
Pencocokan pola agak mirip dengan fungsi overloading, dan saya kira kadang-kadang berfungsi sama. Itu tidak umum karena itu hanya berguna untuk beberapa program dan sulit untuk diterapkan pada sebagian besar bahasa.
Anda lihat ada banyak fitur lain yang lebih mudah diimplementasikan untuk diterapkan ke dalam bahasa, termasuk:
sumber
Jawaban yang bagus! Jawaban A.Rex khususnya sangat rinci dan instruktif. Ketika ia menunjukkan, C ++ tidak mempertimbangkan operator jenis konversi yang disediakan pengguna ketika kompilasi
lhs = func();
(di mana func benar-benar nama struct) . Solusi saya agak berbeda - tidak lebih baik, hanya berbeda (meskipun didasarkan pada ide dasar yang sama).Padahal saya ingin menulis ...
Saya berakhir dengan solusi yang menggunakan struct parameterized (dengan T = tipe pengembalian):
Manfaat dari solusi ini adalah bahwa kode apa pun yang menyertakan definisi templat ini dapat menambahkan lebih banyak spesialisasi untuk lebih banyak jenis. Anda juga dapat melakukan spesialisasi parsial struct jika diperlukan. Misalnya, jika Anda ingin penanganan khusus untuk jenis pointer:
Sebagai negatif, Anda tidak dapat menulis
int x = func();
dengan solusi saya. Anda harus menulisint x = func<int>();
. Anda harus secara eksplisit mengatakan apa jenis kembalinya, daripada meminta kompiler untuk mengeluarkannya dengan melihat pada operator konversi jenis. Saya akan mengatakan bahwa solusi "saya" dan A.Rex keduanya berada di depan optimal untuk mengatasi dilema C ++ ini :)sumber
jika Anda ingin membebani metode dengan tipe pengembalian yang berbeda, cukup tambahkan parameter dummy dengan nilai default untuk memungkinkan eksekusi kelebihan, tetapi jangan lupa jenis parameter harus berbeda sehingga logika kelebihan bekerja selanjutnya adalah eg pada delphi:
gunakan seperti ini
sumber
Seperti yang sudah diperlihatkan - panggilan ambigu dari suatu fungsi yang hanya berbeda dengan tipe kembali memperkenalkan ambiguitas. Ambiguitas menginduksi kode yang rusak. Kode yang rusak harus dihindari.
Kompleksitas yang didorong oleh upaya ambiguitas menunjukkan bahwa ini bukan hack yang baik. Terlepas dari latihan intelektual - mengapa tidak menggunakan prosedur dengan parameter referensi.
sumber
doing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
fitur kelebihan ini tidak sulit untuk dikelola, jika Anda melihatnya dengan cara yang sedikit berbeda. pertimbangkan yang berikut,
jika suatu bahasa memang mengembalikan kelebihan beban, itu akan memungkinkan kelebihan parameter, tetapi bukan duplikasi. ini akan memecahkan masalah:
karena hanya ada satu f (int choice) untuk dipilih.
sumber
Di .NET, terkadang kami menggunakan satu parameter untuk menunjukkan output yang diinginkan dari hasil umum, dan kemudian melakukan konversi untuk mendapatkan apa yang kami harapkan.
C #
Mungkin contoh ini juga bisa membantu:
C ++
sumber
Sebagai catatan, Oktaf memungkinkan hasil yang berbeda sesuai dengan elemen kembali menjadi skalar vs array.
Cf juga Dekomposisi Nilai Singular .
sumber
Yang ini sedikit berbeda untuk C ++; Saya tidak tahu apakah itu akan dianggap kelebihan beban dengan mengetikkan kembali secara langsung. Ini lebih merupakan spesialisasi template yang bertindak dengan cara.
util.h
util.inl
util.cpp
Contoh ini tidak persis menggunakan resolusi fungsi berlebih berdasarkan tipe kembali, namun c ++ kelas non objek ini menggunakan spesialisasi templat untuk mensimulasikan resolusi fungsi berlebih berdasarkan tipe balik dengan metode statis privat.
Setiap
convertToType
fungsi memanggil templat fungsistringToValue()
dan jika Anda melihat rincian implementasi atau algoritme templat fungsi ini, ia memanggilgetValue<T>( param, param )
dan mengembalikan jenisT
dan menyimpannya ke dalamT*
yang dilewatkan kestringToValue()
templat fungsi sebagai salah satu parameternya. .Selain sesuatu seperti ini; C ++ tidak benar-benar memiliki mekanisme untuk memiliki resolusi fungsi overloading oleh tipe kembali. Mungkin ada konstruksi atau mekanisme lain yang tidak saya sadari yang dapat mensimulasikan resolusi berdasarkan tipe pengembalian.
sumber
Saya pikir ini adalah GAP dalam definisi C ++ modern ... mengapa?
Mengapa kompiler C ++ tidak dapat melakukan kesalahan pada contoh "3" dan menerima kode pada contoh "1 + 2" ??
sumber
Sebagian besar bahasa statis sekarang juga mendukung obat generik, yang akan menyelesaikan masalah Anda. Seperti yang dinyatakan sebelumnya, tanpa memiliki parameter diffs, tidak ada cara untuk mengetahui mana yang harus dihubungi. Jadi, jika Anda ingin melakukan ini, gunakan saja obat generik dan sebut itu sehari.
sumber