Kelebihan fungsi? Ya atau tidak [ditutup]

16

Saya sedang mengembangkan bahasa yang dikompilasi secara statis dan sangat diketik, dan saya meninjau kembali gagasan apakah akan memasukkan kelebihan fungsi sebagai fitur bahasa. Saya menyadari bahwa saya sedikit bias, terutama berasal dari C[++|#]latar belakang.

Apa argumen yang paling meyakinkan untuk dan menentang termasuk kelebihan fungsi dalam bahasa?


EDIT: Apakah tidak ada orang yang memiliki pendapat yang berlawanan?

Bertrand Meyer (pencipta Eiffel pada 1985/1986) menyebut metode overloading ini: (sumber)

mekanisme kesombongan yang tidak membawa apa pun pada kekuatan semantik bahasa OO, tetapi menghambat keterbacaan dan mempersulit tugas semua orang

Sekarang itu adalah generalisasi besar, tapi dia pria yang cerdas, jadi saya pikir aman untuk mengatakan dia bisa mendukungnya jika perlu. Bahkan, ia hampir membuat Brad Abrams (salah satu pengembang CLSv1) yakin bahwa .NET seharusnya tidak mendukung metode overloading. (sumber) Itu beberapa hal yang kuat. Adakah yang bisa menjelaskan pemikirannya, dan apakah sudut pandangnya masih bisa dibenarkan 25 tahun kemudian?

Catatan untuk memikirkan nama
sumber

Jawaban:

24

Kelebihan fungsi sangat penting untuk kode template gaya C ++. Jika saya harus menggunakan nama fungsi yang berbeda untuk tipe yang berbeda, saya tidak dapat menulis kode generik. Itu akan menghilangkan sebagian besar dan banyak digunakan perpustakaan C ++, dan banyak fungsi C ++.

Biasanya hadir dalam nama fungsi anggota. A.foo()dapat memanggil fungsi yang sama sekali berbeda B.foo(), tetapi kedua fungsi tersebut dinamai foo. Ini hadir di operator, seperti +halnya hal-hal yang berbeda ketika diterapkan ke bilangan bulat dan angka floating-point, dan sering digunakan sebagai operator penggabungan string. Tampaknya aneh untuk tidak mengizinkannya dalam fungsi reguler juga.

Ini memungkinkan penggunaan Common Lisp-style "multimethods", di mana fungsi yang dipanggil bergantung pada dua tipe data. Jika Anda belum memprogram dalam Common Lisp Object System, coba sebelum Anda menyebutnya tidak berguna. Sangat penting untuk streaming C ++.

I / O tanpa fungsi yang berlebihan (atau fungsi variadik, yang lebih buruk) akan memerlukan sejumlah fungsi yang berbeda, baik untuk mencetak nilai dari tipe yang berbeda atau untuk mengubah nilai dari tipe yang berbeda ke tipe yang umum (seperti String).

Tanpa fungsi yang berlebihan, jika saya mengubah jenis beberapa variabel atau nilai saya perlu mengubah setiap fungsi yang menggunakannya. Itu membuatnya jauh lebih sulit untuk memperbaiki kode.

Itu membuatnya lebih mudah untuk menggunakan API ketika pengguna tidak harus mengingat konvensi penamaan tipe mana yang digunakan, dan pengguna hanya dapat mengingat nama fungsi standar.

Tanpa operator berlebih, kami harus memberi label setiap fungsi dengan jenis yang digunakannya, jika operasi dasar itu dapat digunakan pada lebih dari satu jenis. Ini pada dasarnya adalah notasi Hongaria, cara buruk melakukannya.

Secara keseluruhan, itu membuat bahasa jauh lebih bermanfaat.

David Thornley
sumber
1
+1, semua poin sangat bagus. Dan percayalah, saya rasa multimethod tidak berguna ... Saya mengutuk keyboard yang saya ketik setiap kali saya dipaksa untuk menggunakan pola pengunjung.
Catatan untuk diri sendiri - pikirkan nama
Bukankah orang ini menggambarkan fungsi mengesampingkan dan tidak kelebihan?
dragosb
8

Saya sarankan setidaknya menyadari kelas tipe di Haskell. Kelas-kelas tipe diciptakan untuk menjadi pendekatan disiplin terhadap kelebihan beban operator, tetapi telah menemukan kegunaan lain, dan sampai batas tertentu, membuat Haskell seperti apa adanya.

Misalnya, berikut adalah contoh kelebihan ad-hoc (tidak cukup valid untuk Haskell):

(==) :: Int -> Int -> Bool
x == y = ...
x /= y = not (x == y)

(==) :: Char -> Char -> Bool
x == y = ...
x /= y = not (x == y)

Dan inilah contoh yang sama dari overloading dengan kelas tipe:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

    x /= y  =  not (x == y)

instance Eq Int where
    x == y  = ...

instance Eq Char where
    x == y  = ...

Sebuah Kelemahan dari ini adalah bahwa Anda harus datang dengan nama-nama yang funky untuk semua typeclasses Anda (seperti di Haskell, Anda memiliki lebih abstrak Monad, Functor, Applicativeserta sederhana dan lebih dikenali Eq, Numdan Ord).

Keuntungannya adalah, begitu Anda terbiasa dengan typeclass, Anda tahu cara menggunakan jenis apa pun di kelas itu. Plus, mudah untuk menjaga fungsi dari tipe yang tidak mengimplementasikan kelas yang diperlukan, seperti pada:

group :: (Eq a) => [a] -> [[a]]
group = groupBy (==)

Sunting: Di Haskell, jika Anda menginginkan ==operator yang menerima dua jenis berbeda, Anda bisa menggunakan kelas jenis multi-parameter:

class Eq a b where
    (==) :: a -> b -> Bool
    (/=) :: a -> b -> Bool

    x /= y  =  not (x == y)

instance Eq Int Int where
    x == y  = ...

instance Eq Char Char where
    x == y  = ...

instance Eq Int Float where
    x == y  = ...

Tentu saja, ini mungkin ide yang buruk, karena secara eksplisit memungkinkan Anda membandingkan apel dan jeruk. Namun, Anda mungkin ingin mempertimbangkan hal ini +, karena menambahkan a Word8ke Intbenar - benar adalah hal yang masuk akal untuk dilakukan dalam beberapa konteks.

Joey Adams
sumber
+1, saya sudah mencoba memahami konsep ini sejak pertama kali membacanya, dan ini membantu. Apakah paradigma ini membuat mustahil untuk mendefinisikan, misalnya, di (==) :: Int -> Float -> Boolmana saja? (terlepas dari apakah itu ide yang bagus, tentu saja)
Catatan untuk diri sendiri - pikirkan nama
Jika Anda mengizinkan kelas tipe multi-parameter (yang didukung Haskell sebagai ekstensi), Anda bisa. Saya memperbarui jawabannya dengan sebuah contoh.
Joey Adams
Hmm menarik. Jadi pada dasarnya, class Eq a ...diterjemahkan ke dalam pseudo-C-family akan menjadi interface Eq<A> {bool operator==(A x, A y);}, dan alih-alih menggunakan kode templated untuk membandingkan objek sewenang-wenang, Anda menggunakan 'antarmuka' ini. Apakah itu benar?
Catatan untuk diri - pikirkan nama
Baik. Anda mungkin juga ingin melihat antarmuka di Go. Yaitu, Anda tidak harus mendeklarasikan tipe sebagai mengimplementasikan antarmuka, Anda hanya perlu mengimplementasikan semua metode untuk antarmuka itu.
Joey Adams
1
@ Notetoself-thinkofaname: Mengenai operator tambahan - ya dan tidak. Itu memungkinkan untuk ==berada di ruang nama yang berbeda tetapi tidak memungkinkan untuk menimpanya. Harap perhatikan bahwa ada satu namespace ( Prelude) yang disertakan secara default tetapi Anda dapat mencegah memuatnya dengan menggunakan ekstensi atau dengan mengimpornya secara eksplisit (tidak import Prelude ()akan mengimpor apa pun dari Preludedan import qualified Prelude as Ptidak akan memasukkan simbol ke namespace saat ini).
Maciej Piechotka
4

Izinkan fungsi berlebihan, Anda tidak dapat melakukan hal berikut dengan parameter opsional (atau jika Anda bisa, tidak baik).

contoh sepele mengasumsikan tidak ada base.ToString() metode

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...
Biner Terburuk
sumber
Untuk bahasa yang sangat diketik, ya. Untuk bahasa yang diketik dengan lemah, tidak. Anda dapat melakukan hal di atas dengan hanya satu fungsi dalam bahasa yang diketik dengan lemah.
spex
2

Saya selalu lebih suka parameter default daripada fungsi yang berlebihan. Fungsi kelebihan beban umumnya hanya memanggil versi "default" dengan parameter default. Kenapa menulis

int indexOf(char ch)
{
  return self.indexOf(ch, 0);
}

int indexOf(char ch, int fromIndex)
{
  // Do whatever
}

Ketika saya bisa melakukan:

int indexOf(char ch, int fromIndex=0)
{
  // Do whatever
}

Yang mengatakan, saya menyadari kadang-kadang kelebihan fungsi melakukan hal-hal yang berbeda, daripada hanya memanggil varian lain dengan parameter default ... tetapi dalam kasus seperti itu, itu bukan ide yang buruk (pada kenyataannya, itu mungkin ide yang baik ) untuk hanya memberikannya nama yang berbeda.

(Juga, argumen kata kunci gaya Python bekerja sangat baik dengan parameter default.)

mipadi
sumber
Oke, mari kita coba lagi, dan saya akan mencoba memahami kali ini ... Bagaimana dengan Array Slice(int start, int length) {...}kelebihan beban Array Slice(int start) {return this.Slice(start, this.Count - start);}? Itu tidak bisa dikodekan menggunakan parameter default. Apakah Anda pikir mereka harus diberi nama yang berbeda? Jika demikian, bagaimana Anda memberi nama mereka?
Catatan untuk diri sendiri - pikirkan nama
Itu tidak membahas semua penggunaan kelebihan beban.
MetalMikester
@MetalMikester: Apa gunanya yang menurut Anda saya lewatkan?
mipadi
@mipadi Merindukan hal-hal seperti indexOf(char ch)+ indexOf(Date dt)pada daftar. Saya suka nilai default juga, tetapi tidak bisa ditukar dengan pengetikan statis.
Tandai
2

Anda baru saja menggambarkan Java. Atau C #.

Mengapa Anda menemukan kembali kemudi?

Pastikan bahwa tipe pengembalian adalah bagian dari tanda tangan metode dan Overload ke isi hati Anda, itu benar-benar membersihkan kode ketika Anda tidak perlu mengatakannya.

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }
Josh K.
sumber
7
Ada alasan mengapa tipe pengembalian bukan bagian dari tanda tangan metode - atau setidaknya bukan bagian yang dapat digunakan untuk resolusi kelebihan beban - dalam bahasa apa pun yang saya ketahui. Ketika Anda memanggil fungsi sebagai prosedur, tanpa menetapkan hasil ke variabel atau properti, bagaimana kompiler seharusnya mengetahui versi mana yang akan dipanggil jika semua argumen lain identik?
Mason Wheeler
@ Alasan: Anda bisa mendeteksi tipe pengembalian yang diharapkan secara heuristik berdasarkan apa yang diharapkan akan dikembalikan, namun saya tidak berharap itu dilakukan.
Josh K
1
Umm ... bagaimana heuristik Anda tahu apa yang diharapkan dikembalikan ketika Anda tidak mengharapkan nilai pengembalian?
Mason Wheeler
1
Heuristik tipe-harapan sebenarnya ada di tempat ... Anda dapat melakukan hal-hal seperti EnumX.Flag1 | Flag2 | Flag3. Saya tidak akan mengimplementasikan ini, meskipun. Jika saya melakukannya dan jenis kembali tidak digunakan, saya akan mencari jenis kembali void.
Catatan untuk diri sendiri - pikirkan nama
1
@ Alasan: Itu pertanyaan yang bagus, tetapi dalam hal ini saya akan mencari fungsi yang tidak berlaku (seperti yang disebutkan). Secara teori, Anda dapat memilih salah satu dari mereka, karena mereka semua akan melakukan fungsi yang sama, cukup mengembalikan data dalam format yang berbeda.
Josh K
2

Grrr .. belum cukup privilege untuk berkomentar ..

@Mason Wheeler: Waspadalah terhadap Ada, yang melakukan overload pada tipe kembali. Juga bahasa saya Felix melakukannya juga dalam beberapa konteks, khususnya, ketika suatu fungsi mengembalikan fungsi lain dan ada panggilan seperti:

f a b  // application is left assoc: (f a) b

tipe b dapat digunakan untuk resolusi kelebihan beban. C ++ juga kelebihan pada tipe pengembalian dalam beberapa keadaan:

int (*f)(int) = g; // choses g based on type, not just signature

Bahkan ada algoritma untuk overloading pada tipe kembali, menggunakan inferensi tipe. Sebenarnya tidak terlalu sulit untuk dilakukan dengan mesin, masalahnya adalah manusia merasa sulit. (Saya pikir garis besarnya diberikan dalam Buku Naga, algoritma ini disebut algoritma lihat-lihat jika saya ingat dengan benar)

Yttrill
sumber
2

Kegunaan terhadap implementasi fungsi overloading: 25 metode yang dinamai seperti itu melakukan hal yang sama tetapi dengan set argumen yang sangat berbeda dalam berbagai pola.

Use-case terhadap tidak menerapkan fungsi overloading: 5 metode yang bernama sama dengan set tipe yang sangat mirip dalam pola yang sama persis.

Pada akhirnya, saya tidak sabar untuk membaca dokumen untuk API yang diproduksi dalam kedua kasus tersebut.

Tetapi dalam satu kasus ini tentang apa yang mungkin dilakukan pengguna. Dalam kasus lain itu yang harus dilakukan pengguna karena pembatasan bahasa. IMO, lebih baik untuk setidaknya memungkinkan kemungkinan penulis program cukup pintar untuk membebani secara masuk akal tanpa menciptakan ambiguitas. Ketika Anda menampar tangan dan mengambil opsi itu, pada dasarnya Anda menjamin ambiguitas akan terjadi. Saya lebih tentang mempercayai pengguna untuk melakukan hal yang benar daripada menganggap mereka akan selalu melakukan hal yang salah. Dalam pengalaman saya, proteksionisme cenderung mengarah pada perilaku yang bahkan lebih buruk dari komunitas bahasa.

Erik Reppen
sumber
1

Saya memilih untuk memberikan kelas tipe-overloading dan multi-tipe biasa dalam bahasa saya, Felix.

Saya menganggap (terbuka) kelebihan beban penting, terutama dalam bahasa yang banyak jenis numeriknya (Felix memiliki semua tipe numerik C). Namun tidak seperti C ++ yang menyalahgunakan kelebihan dengan membuat templat bergantung padanya, Felix polimorfisme adalah parametrik: Anda perlu kelebihan muatan untuk templat di C ++ karena templat di C ++ dirancang dengan buruk.

Kelas tipe disediakan di Felix juga. Bagi mereka yang tahu C ++ tetapi tidak grok Haskell, abaikan mereka yang menggambarkannya sebagai overloading. Ini bukan seperti overloading, melainkan seperti spesialisasi templat: Anda mendeklarasikan templat yang tidak Anda terapkan, lalu memberikan implementasi untuk kasus-kasus tertentu sesuai kebutuhan Anda. Pengetikan bersifat parametrik polimorfik, implementasinya adalah dengan instantiasi ad hoc tetapi tidak dimaksudkan untuk tidak dibatasi: ia harus mengimplementasikan semantik yang dimaksud.

Di Haskell (dan C ++) Anda tidak bisa menyatakan semantiknya. Dalam C ++ ide "Konsep" kira-kira merupakan upaya untuk mendekati semantik. Dalam Felix, Anda dapat memperkirakan maksud dengan aksioma, reduksi, lemma, dan teorema.

Keuntungan utama, dan satu - satunya kelebihan (terbuka) dalam bahasa berprinsip baik seperti Felix adalah membuatnya lebih mudah untuk mengingat nama fungsi pustaka, baik untuk penulis program dan untuk peninjau kode.

Kerugian utama dari kelebihan beban adalah algoritma kompleks yang diperlukan untuk mengimplementasikannya. Itu juga tidak cocok dengan inferensi tipe: meskipun keduanya tidak sepenuhnya eksklusif, algoritma untuk melakukan keduanya cukup kompleks, pemrogram mungkin tidak akan dapat memprediksi hasilnya.

Dalam C ++ ini juga masalah karena memiliki algoritma pencocokan ceroboh dan juga mendukung konversi tipe otomatis: di Felix I "memperbaiki" masalah ini dengan memerlukan pencocokan yang tepat dan tidak ada konversi tipe otomatis.

Jadi Anda punya pilihan saya pikir: overloading atau ketik inferensi. Inferensi itu lucu, tetapi juga sangat sulit untuk diterapkan dengan cara yang mendiagnosis konflik dengan benar. Ocaml, misalnya, memberi tahu Anda di mana ia mendeteksi konflik, tetapi tidak dari mana ia menyimpulkan tipe yang diharapkan.

Overloading tidak jauh lebih baik, bahkan jika Anda memiliki kompiler berkualitas yang mencoba memberi tahu Anda semua kandidat, mungkin sulit dibaca jika kandidatnya polimorfik, dan lebih buruk lagi jika itu adalah peretas template C ++.

Yttrill
sumber
Kedengarannya menarik. Saya ingin membaca lebih lanjut, tetapi tautan ke dokumen di halaman web Felix terputus.
Catatan untuk diri sendiri - pikirkan nama
Ya, seluruh situs saat ini sedang dibangun (lagi), maaf.
Yttrill
0

Turun ke konteks, tapi saya pikir kelebihan membuat kelas jauh lebih berguna ketika saya menggunakan satu yang ditulis oleh orang lain. Anda sering berakhir dengan redundansi yang lebih sedikit.

Morgan Herlocker
sumber
0

Jika Anda ingin memiliki pengguna yang terbiasa dengan bahasa C-family maka ya Anda harus melakukannya karena pengguna Anda akan mengharapkannya.

Conrad Frix
sumber