Apakah metode kelebihan muatan lebih dari sekadar gula sintaksis? [Tutup]

19

Apakah metode kelebihan jenis polimorfisme? Bagi saya sepertinya hanya diferensiasi metode dengan nama dan parameter yang sama. Jadi stuff(Thing t)dan stuff(Thing t, int n)metode yang sama sekali berbeda sejauh menyangkut kompiler dan runtime.

Ini menciptakan ilusi, di sisi penelepon, bahwa itu adalah metode yang sama yang bertindak berbeda pada berbagai jenis objek - polimorfisme. Tapi itu hanya ilusi, karena sebenarnya stuff(Thing t)dan stuff(Thing t, int n)metode yang sama sekali berbeda.

Apakah metode kelebihan muatan lebih dari sekadar gula sintaksis? Apakah saya melewatkan sesuatu?


Definisi umum untuk gula sintaksis, adalah bahwa gula itu murni lokal . Berarti mengubah sepotong kode ke 'manisnya' yang setara, atau sebaliknya, melibatkan perubahan lokal yang tidak mempengaruhi struktur keseluruhan program. Dan saya pikir metode overloading cocok dengan kriteria ini. Mari kita lihat contoh untuk menunjukkan:

Pertimbangkan kelas:

class Reader {
    public String read(Book b){
        // .. translate the book to text
    }
    public String read(File b){
        // .. translate the file to text
    }
}

Sekarang pertimbangkan kelas lain yang menggunakan kelas ini:

/* might not be the best example */
class FileProcessor {
    Reader reader = new Reader();
    public void process(File file){
        String text = reader.read(file);
        // .. do stuff with the text
    }
}

Baik. Sekarang mari kita lihat apa yang perlu diubah jika kita mengganti metode overloading dengan metode biasa:

The readmetode dalam Readerperubahan readBook(Book)dan readFile(file). Hanya masalah mengubah nama mereka.

Kode panggilan FileProcessorsedikit reader.read(file)berubah : berubah menjadi reader.readFile(file).

Dan itu saja.

Seperti yang Anda lihat, perbedaan antara menggunakan metode overloading dan tidak menggunakannya, adalah murni lokal . Dan itulah mengapa saya pikir itu memenuhi syarat sebagai gula sintaksis murni.

Saya ingin mendengar keberatan Anda jika ada, mungkin saya kehilangan sesuatu.

Aviv Cohn
sumber
48
Pada akhirnya, setiap fitur bahasa pemrograman hanyalah gula sintaksis untuk assembler mentah.
Philipp
31
@ Pilip: Maaf, tapi itu pernyataan yang benar-benar bodoh. Bahasa pemrograman mendapatkan kegunaannya dari semantik, bukan sintaksis. Fitur seperti sistem tipe memberi Anda jaminan yang sebenarnya, meskipun sebenarnya mungkin mengharuskan Anda untuk menulis lebih banyak .
back2dos
3
Tanyakan pada diri Anda sendiri: Apakah operator kelebihan gula hanya sintaksis? Apa pun jawaban untuk pertanyaan yang Anda yakini juga merupakan jawaban untuk pertanyaan yang Anda ajukan;)
back2dos
5
@ back2dos: Sepenuhnya setuju dengan Anda. Saya membaca kalimat "semuanya hanya gula sintaksis untuk assembler" terlalu sering, dan itu jelas salah. Gula sintaksis adalah sintaksis alternatif (mungkin lebih baik) untuk beberapa sintaksis yang ada yang tidak menambahkan semantik baru.
Giorgio
6
@Iorgio: benar! Ada definisi yang tepat dalam makalah tengara Matthias Felleisen tentang ekspresivitas. Pada dasarnya: gula sintaksis murni lokal. Jika Anda harus mengubah struktur global program untuk menghapus penggunaan fitur bahasa, maka itu bukan gula sintaksis. Yaitu menulis ulang kode OO polimorfik dalam assembler biasanya melibatkan penambahan logika pengiriman global, yang tidak murni lokal, oleh karena itu OO bukan "hanya gula sintaksis untuk assembler".
Jörg W Mittag

Jawaban:

29

Untuk menjawab ini, pertama Anda perlu definisi untuk "gula sintaksis." Saya akan menggunakan Wikipedia :

Dalam ilmu komputer, gula sintaksis adalah sintaksis dalam bahasa pemrograman yang dirancang untuk membuat segala sesuatu lebih mudah dibaca atau diungkapkan. Itu membuat bahasa "lebih manis" untuk digunakan manusia: hal-hal dapat diekspresikan lebih jelas, lebih ringkas, atau dalam gaya alternatif yang mungkin disukai beberapa orang.

[...]

Secara khusus, sebuah konstruksi dalam bahasa disebut gula sintaksis jika dapat dihapus dari bahasa tanpa efek pada apa yang dapat dilakukan bahasa

Jadi, di bawah definisi ini, fitur-fitur seperti Java varargs atau Scala untuk pemahaman adalah gula sintaksis: mereka menerjemahkan ke dalam fitur bahasa yang mendasarinya (array dalam kasus pertama, panggilan untuk memetakan / flatmap / filter dalam yang kedua), dan menghapusnya akan tidak mengubah hal-hal yang dapat Anda lakukan dengan bahasa tersebut.

Metode overloading, bagaimanapun, bukanlah gula sintaksis di bawah definisi ini, karena menghapusnya secara mendasar akan mengubah bahasa (Anda tidak akan lagi dapat mengirim ke perilaku yang berbeda berdasarkan argumen).

Benar, Anda dapat mensimulasikan overloading metode selama Anda memiliki beberapa cara untuk mengakses argumen metode, dan dapat menggunakan konstruksi "jika" berdasarkan argumen yang Anda berikan. Tetapi jika Anda menganggap itu gula sintaksis, Anda harus mempertimbangkan apa pun di atas mesin Turing untuk menjadi gula sintaksis.

kdgregory
sumber
22
Menghapus kelebihan beban tidak akan mengubah apa yang bisa dilakukan bahasa. Anda masih bisa melakukan hal yang persis sama seperti sebelumnya; Anda hanya perlu mengganti nama beberapa metode. Itu perubahan yang lebih sepele dari pada loop keinginan.
Doval
9
Seperti yang saya katakan, Anda bisa mengambil pendekatan bahwa semua bahasa (termasuk bahasa mesin) hanyalah gula sintaksis di atas mesin Turing.
kdgregory
9
Seperti yang saya lihat metode overloading hanya memungkinkan Anda melakukannya sum(numbersArray)dan sum(numbersList)bukannya sumArray(numbersArray)dan sumList(numbersList). Saya setuju dengan Doval, sepertinya gula sintaksis belaka.
Aviv Cohn
3
Sebagian besar bahasanya. Mencoba menerapkan instanceof, kelas, warisan, antarmuka, obat generik, refleksi atau penspesifikasi akses menggunakan if, whiledan operator boolean, dengan semantik yang sama persis . Tidak ada kasus sudut. Perhatikan bahwa saya tidak menantang Anda untuk menghitung hal yang sama seperti penggunaan spesifik konstruksi tersebut. Saya sudah tahu Anda bisa menghitung apa saja menggunakan logika boolean dan branching / looping. Saya meminta Anda untuk menerapkan salinan semantik yang sempurna dari fitur-fitur bahasa tersebut, termasuk jaminan statis yang disediakannya (kompilasi pemeriksaan waktu masih harus dilakukan pada waktu kompilasi.)
Doval
6
@Doval, kdgregory: Untuk mendefinisikan gula sintaksis Anda harus mendefinisikannya relatif terhadap beberapa semantik. Jika satu-satunya semantik yang Anda miliki adalah "Apa yang dihitung program ini?", Maka jelas bahwa semuanya hanyalah gula sintaksis untuk mesin Turing. Di sisi lain, jika Anda memiliki semantik di mana Anda dapat berbicara tentang objek dan operasi tertentu pada mereka, maka menghapus sintaksis tertentu tidak akan memungkinkan Anda untuk mengekspresikan operasi itu lagi, meskipun bahasanya masih bisa selesai-Turing.
Giorgio
13

Istilah gula sintaksis biasanya merujuk pada kasus-kasus di mana fitur didefinisikan oleh substitusi. Bahasa tidak mendefinisikan apa yang dilakukan fitur, melainkan mendefinisikan bahwa itu persis setara dengan sesuatu yang lain. Jadi misalnya, untuk setiap loop

for(Object alpha: alphas) {
}

Menjadi:

for(Iterator<Object> iter = alpha.iterator(); iter.hasNext()) {
   alpha = iter.next();
}

Atau ambil fungsi dengan argumen variabel:

void foo(int... args);

foo(3, 4, 5);

Yang menjadi:

void Foo(int[] args);

foo(new int[]{3, 4, 5});

Jadi ada subtitusi sintaksis sepele untuk mengimplementasikan fitur dalam hal fitur lainnya.

Mari kita lihat metode overloading.

void foo(int a);
void foo(double b);

foo(4.5);

Ini dapat ditulis ulang sebagai:

void foo_int(int a);
void foo_double(double b);

foo_double(4.5);

Tapi itu tidak setara dengan itu. Dalam model Jawa, ini adalah sesuatu yang berbeda. foo(int a)tidak mengimplementasikan foo_intfungsi yang akan dibuat. Java tidak menerapkan metode overloading dengan memberi fungsi ambigu nama lucu. Untuk menghitung sebagai gula sintaksis, java harus berpura-pura bahwa Anda benar-benar menulis foo_intdan foo_doubleberfungsi tetapi tidak.

Winston Ewert
sumber
2
Saya tidak berpikir ada orang yang mengatakan transformasi untuk sintaksis harus sepele. Bahkan jika itu terjadi, saya menemukan klaim yang But, the transformation isn't trivial. At the least, you have to determine the types of the parameters.sangat samar karena jenisnya tidak perlu ditentukan ; mereka dikenal pada waktu kompilasi.
Doval
3
"Untuk menghitung sebagai gula sintaksis, java harus berpura-pura bahwa Anda benar-benar menulis fungsi foo_int dan foo_double tetapi tidak." - selama kita berbicara tentang metode kelebihan beban dan bukan polimorfisme, apa perbedaan antara foo(int)/ foo(double)dan foo_int/ foo_double? Saya tidak benar-benar mengenal Java dengan sangat baik tetapi saya akan membayangkan bahwa penggantian nama seperti itu benar-benar terjadi di JVM (well - mungkin menggunakan foo(args)lebih dari itu foo_args- itu setidaknya dalam C ++ dengan simbol mangling (ok - simbol mangling secara teknis merupakan detail implementasi dan bukan bagian bahasa)
Maciej Piechotka
2
@Doval: "Saya tidak berpikir ada orang yang mengatakan transformasi untuk sintaksis harus sepele." - Benar, tapi itu harus lokal . Satu-satunya definisi yang berguna dari gula sintaksis yang saya tahu adalah dari makalah Matthias Felleisen yang terkenal tentang ekspresivitas bahasa, dan pada dasarnya dikatakan bahwa jika Anda dapat menulis ulang program yang ditulis dalam bahasa L + y (yaitu beberapa bahasa L dengan beberapa fitur y ) di bahasa L (yaitu himpunan bagian dari bahasa itu tanpa fitur y ) tanpa mengubah struktur global program (yaitu hanya membuat perubahan lokal), maka y adalah gula sintaksis dalam L + y dan tidak
Jörg W Mittag
2
… Tidak meningkatkan ekspresifitas L. Namun, jika Anda tidak bisa melakukan itu, yaitu jika Anda harus membuat perubahan struktur global dari program Anda, maka itu tidak sintaksis gula dan tidak pada kenyataannya make L + y lebih ekspresif daripada L . Sebagai contoh, Java dengan forloop yang disempurnakan tidak lebih ekspresif daripada Java tanpa itu. (Ini lebih bagus, lebih ringkas, lebih mudah dibaca, dan lebih baik, saya berpendapat, tapi tidak lebih ekspresif.) Namun, saya tidak yakin dengan kasus kelebihan beban. Saya mungkin harus membaca ulang koran untuk memastikan. Menurut saya itu adalah gula sintaksis, tetapi saya tidak yakin.
Jörg W Mittag
2
@MaciejPiechotka, jika itu adalah bagian dari definisi bahasa yang fungsinya diubah namanya, dan Anda dapat mengakses fungsi di bawah nama-nama itu, saya pikir itu akan menjadi gula sintaksis. Tetapi karena tersembunyi sebagai detail implementasi, saya pikir itu mendiskualifikasi dari menjadi gula sintaksis.
Winston Ewert
8

Mengingat nama itu berfungsi, bukankah itu tidak lebih dari gula sintaksis?

Ini memungkinkan penelepon untuk membayangkan dia memanggil fungsi yang sama, ketika dia tidak. Tapi ia bisa mengetahui yang sebenarnya nama-nama dari semua fungsi nya. Hanya jika dimungkinkan untuk mencapai polimorfisme yang tertunda dengan mengirimkan variabel yang tidak diketik ke dalam fungsi yang diketik dan jenisnya ditetapkan sehingga panggilan bisa pergi ke versi yang tepat sesuai dengan nama maka ini akan menjadi fitur bahasa yang sebenarnya.

Sayangnya, saya belum pernah melihat bahasa melakukan ini. Ketika ada ambiguitas, kompiler ini tidak menyelesaikannya, mereka mendesak penulis untuk menyelesaikannya.

Jon Jay Obermark
sumber
Fitur yang Anda cari di sana disebut "Pengiriman Ganda". Banyak bahasa yang mendukungnya termasuk Haskell, Scala, dan (sejak 4.0) C #.
Iain Galloway
Saya ingin memisahkan parameter pada kelas dari overloading metode lurus. Dalam kasus overloading metode lurus, programmer menulis semua versi, kompiler hanya tahu bagaimana memilih satu. Itu hanya gula sintaksis, dan diselesaikan dengan nama-mangling sederhana, bahkan untuk pengiriman ganda. --- Di hadapan parameter pada kelas, kompiler menghasilkan kode yang diperlukan, dan itu mengubah ini sepenuhnya.
Jon Jay Obermark
2
Saya pikir Anda salah paham. Misalnya, dalam C #, jika salah satu parameter untuk suatu metode dynamickemudian resolusi kelebihan terjadi pada saat runtime, bukan pada waktu kompilasi . Itulah multi-pengiriman, dan tidak dapat direplikasi dengan mengganti nama fungsi.
Iain Galloway
Cukup keren. Saya masih bisa menguji untuk tipe variabel, jadi ini masih fungsi bawaan overlay pada gula sintaksis. Ini adalah fitur bahasa, tetapi hanya nyaris.
Jon Jay Obermark
7

Tergantung pada bahasanya, itu adalah gula sintaksis atau tidak.

Di C ++ misalnya, Anda dapat melakukan hal-hal menggunakan kelebihan beban dan templat yang tidak akan mungkin terjadi tanpa komplikasi (tulis secara manual semua instantiasi templat atau tambahkan banyak parameter templat).

Perhatikan bahwa pengiriman dinamis adalah bentuk kelebihan muatan, diselesaikan secara dinamis pada beberapa parameter (untuk beberapa bahasa hanya yang khusus, ini , tetapi tidak semua bahasa sangat terbatas), dan saya tidak akan menyebut bentuk kelebihan sintaksis gula.

Pemrogram
sumber
Saya tidak yakin bagaimana jawaban lain melakukan jauh lebih baik ketika pada dasarnya salah.
Telastyn
5

Untuk bahasa kontemporer, itu hanya gula sintaksis; dalam cara bahasa-agnostik sepenuhnya, itu lebih dari itu.

Sebelumnya jawaban ini hanya menyatakan bahwa itu lebih dari sekadar gula sintaksis, tetapi jika Anda akan melihat di komentar, Falco mengemukakan masalah bahwa ada satu bagian teka-teki yang sepertinya tidak ada dalam bahasa kontemporer; mereka tidak mencampur metode overloading dengan penentuan dinamis fungsi mana yang dipanggil dalam langkah yang sama. Ini akan diklarifikasi nanti.

Inilah mengapa itu harus lebih.

Pertimbangkan bahasa yang mendukung variabel overloading dan variabel yang tidak diketik. Anda dapat memiliki prototipe metode berikut:

bool someFunction(int arg);

bool someFunction(string arg);

Dalam beberapa bahasa, Anda mungkin akan pasrah mengetahui pada waktu kompilasi mana yang akan dipanggil oleh baris kode tertentu. Tetapi dalam beberapa bahasa, tidak semua variabel diketik (atau mereka semua secara implisit diketik sebagai Objectatau apa pun), jadi bayangkan membangun kamus yang kuncinya memetakan nilai-nilai dari jenis yang berbeda:

dict roomNumber; // some hotels use numbers, some use letters, and some use
                 // alphanumerical strings.  In some languages, built-in dictionary
                 // types automatically use untyped values for their keys to map to,
                 // so it makes more sense then to allow for both ints and strings in
                 // your code.

Nah, bagaimana jika Anda ingin menerapkan someFunctionke salah satu nomor kamar itu? Anda menyebutnya:

someFunction(roomNumber[someSortOfKey]);

Apakah someFunction(int)disebut, atau someFunction(string)disebut? Di sini Anda melihat satu contoh di mana ini bukan metode yang sepenuhnya ortogonal, terutama dalam bahasa tingkat tinggi. Bahasa harus mencari tahu - selama runtime - mana yang harus dipanggil, jadi masih harus menganggap ini sebagai metode yang setidaknya sama.

Mengapa tidak menggunakan templat saja? Mengapa tidak menggunakan argumen yang tidak diketik?

Fleksibilitas dan kontrol yang lebih halus. Terkadang menggunakan template / argumen yang tidak diketik merupakan pendekatan yang lebih baik, tetapi terkadang tidak.

Anda harus memikirkan kasus-kasus di mana, misalnya, Anda mungkin memiliki dua tanda tangan metode yang masing-masing mengambil argumen inta dan a string, tetapi urutannya berbeda di setiap tanda tangan. Anda mungkin memiliki alasan yang kuat untuk melakukan ini, karena implementasi setiap tanda tangan mungkin melakukan hal yang sama, tetapi dengan sedikit perbedaan; penebangan bisa berbeda, misalnya. Atau bahkan jika mereka melakukan hal yang persis sama, Anda mungkin dapat secara otomatis mengumpulkan informasi tertentu hanya dari urutan argumen yang ditentukan. Secara teknis Anda hanya bisa menggunakan pernyataan pseudo-switch untuk menentukan jenis setiap argumen yang diteruskan, tetapi itu menjadi berantakan.

Jadi apakah ini contoh praktik pemrograman yang buruk selanjutnya?

bool stringIsTrue(int arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(Object arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(string arg)
{
    if (arg == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

Ya, pada umumnya. Dalam contoh khusus ini, ini dapat mencegah seseorang mencoba menerapkan ini pada tipe primitif tertentu dan mendapatkan kembali perilaku yang tidak terduga (yang bisa menjadi hal yang baik); tapi anggap saja saya menyingkat kode di atas, dan bahwa Anda, pada kenyataannya, memiliki kelebihan untuk semua jenis primitif, serta untuk Objects. Maka bit kode berikut ini benar-benar lebih tepat:

bool stringIsTrue(untyped arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

Tetapi bagaimana jika Anda hanya perlu menggunakan ini untuk ints dan strings, dan bagaimana jika Anda ingin mengembalikannya berdasarkan pada kondisi yang lebih sederhana atau lebih rumit? Maka Anda memiliki alasan yang bagus untuk menggunakan kelebihan beban:

bool appearsToBeFirstFloor(int arg)
{
    if (arg.digitAt(0) == 1)
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool appearsToBeFirstFloor(string arg)
{
    string firstCharacter = arg.characterAt(0);
    if (firstCharacter.isDigit())
    {
        return appearsToBeFirstFloor(int(firstCharacter));
    }
    else if (firstCharacter.toUpper() == "A")
    {
        return true;
    }
    else
    {
        return false;
    }
}

Tapi hei, mengapa tidak memberikan fungsi-fungsi itu dua nama yang berbeda? Anda masih memiliki jumlah kendali yang sama, bukan?

Karena, seperti yang dinyatakan sebelumnya, beberapa hotel menggunakan angka, beberapa menggunakan huruf, dan beberapa menggunakan campuran angka dan huruf:

appearsToBeFirstFloor(roomNumber[someSortOfKey]);

// will treat ints and strings differently, without you having to write extra code
// every single spot where the function is being called

Ini masih bukan kode persis sama persis yang akan saya gunakan dalam kehidupan nyata, tetapi harus menggambarkan hal yang saya buat dengan baik.

Tapi ... Inilah sebabnya mengapa itu tidak lebih dari gula sintaksis dalam bahasa kontemporer.

Falco mengangkat pokok bahasan di komentar bahwa bahasa saat ini pada dasarnya tidak mencampur metode overloading dan pemilihan fungsi dinamis dalam langkah yang sama. Cara saya sebelumnya memahami bahasa tertentu untuk bekerja adalah bahwa Anda dapat membebani appearsToBeFirstFloordalam contoh di atas, dan kemudian bahasa akan menentukan pada saat runtime versi fungsi yang akan dipanggil, tergantung pada nilai runtime dari variabel yang tidak diketik. Kebingungan ini sebagian berasal dari bekerja dengan ECMA-macam bahasa, seperti ActionScript 3.0, di mana Anda dapat dengan mudah mengacak fungsi mana dipanggil pada baris kode tertentu saat runtime.

Seperti yang Anda ketahui, ActionScript 3 tidak mendukung kelebihan metode. Adapun VB.NET, Anda dapat mendeklarasikan dan mengatur variabel tanpa menetapkan jenis secara eksplisit, tetapi ketika Anda mencoba untuk mengirimkan variabel-variabel ini sebagai argumen untuk metode kelebihan beban, masih tidak ingin membaca nilai runtime untuk menentukan metode yang akan dipanggil; alih-alih ingin menemukan metode dengan argumen tipe Objectatau tanpa tipe atau yang lain seperti itu. Jadi contoh intvs. di stringatas tidak akan berfungsi dalam bahasa itu juga. C ++ memiliki masalah serupa, seperti ketika Anda menggunakan sesuatu seperti void pointer atau mekanisme lain seperti itu, C ++ masih mengharuskan Anda untuk secara manual mengaburkan tipe pada waktu kompilasi.

Jadi seperti tajuk pertama mengatakan ...

Untuk bahasa kontemporer, itu hanya gula sintaksis; dalam cara bahasa-agnostik sepenuhnya, itu lebih dari itu. Membuat metode overloading lebih bermanfaat dan relevan, seperti dalam contoh di atas, sebenarnya bisa menjadi fitur yang baik untuk ditambahkan ke bahasa yang ada (seperti yang secara luas diminta untuk AS3), atau itu juga bisa berfungsi sebagai salah satu di antara banyak pilar fundamental berbeda untuk penciptaan bahasa berorientasi prosedural / objek baru.

Panzercrisis
sumber
3
Bisakah Anda menyebutkan bahasa yang benar-benar menangani Function-Dispatch saat runtime dan bukan waktu kompilasi? SEMUA bahasa yang saya tahu memerlukan kepastian waktu kompilasi fungsi yang disebut ...
Falco
@ Falco ActionScript 3.0 menanganinya saat runtime. Anda bisa, misalnya, menggunakan fungsi yang kembali salah satu dari tiga string secara acak, dan kemudian menggunakan nilai kembali untuk memanggil salah satu dari tiga fungsi secara acak: this[chooseFunctionNameAtRandom](); Jika chooseFunctionNameAtRandom()pengembalian baik "punch", "kick"atau "dodge", maka Anda dapat thusly menerapkan sangat simple random elemen dalam, misalnya, AI musuh dalam game Flash.
Panzercrisis
1
Ya - tetapi keduanya merupakan metode semantik nyata untuk mendapatkan fungsi pengiriman dinamis, Java juga memilikinya. Tetapi mereka berbeda dari overloading, overloading statis dan hanya gula sintaksis, sementara pengiriman dinamis dan pewarisan adalah fitur bahasa nyata, yang menawarkan fungsionalitas baru!
Falco
1
... Saya juga mencoba void pointer di C + +, serta pointer kelas dasar, tetapi kompiler ingin saya untuk ambigu sendiri sebelum meneruskannya ke fungsi. Jadi sekarang saya bertanya-tanya apakah akan menghapus jawaban ini. Ini mulai terlihat seperti bahasa yang hampir selalu berjalan hingga menggabungkan pilihan fungsi dinamis dengan fungsi yang berlebihan dalam instruksi atau pernyataan yang sama, tetapi kemudian menjauh pada detik terakhir. Ini akan menjadi fitur bahasa yang bagus; mungkin seseorang perlu membuat bahasa yang memiliki ini.
Panzercrisis
1
Biarkan jawabannya tetap, mungkin berpikir tentang memasukkan beberapa riset Anda dari komentar dalam jawaban?
Falco
2

Itu benar-benar tergantung pada definisi Anda tentang "gula sintaksis". Saya akan mencoba membahas beberapa definisi yang muncul di benak saya:

  1. Suatu fitur adalah gula sintaksis ketika suatu program yang menggunakannya selalu dapat diterjemahkan dalam bahasa lain yang tidak menggunakan fitur tersebut.

    Di sini kita mengasumsikan bahwa ada seperangkat fitur primitif yang tidak dapat diterjemahkan: dengan kata lain tidak ada loop dari jenis "Anda dapat mengganti fitur X menggunakan fitur Y" dan "Anda dapat mengganti fitur Y dengan fitur X". Jika salah satu dari keduanya benar dari salah satu fitur yang lain dapat dinyatakan dalam hal fitur yang bukan yang pertama atau itu adalah fitur primitif.

  2. Sama seperti definisi 1 tetapi dengan persyaratan tambahan bahwa program yang diterjemahkan adalah jenis-aman sebagai yang pertama, yaitu dengan keinginan Anda tidak kehilangan segala jenis informasi.

  3. Definisi OP: fitur adalah gula sintaksis jika terjemahannya tidak mengubah struktur program tetapi hanya membutuhkan "perubahan lokal".

Mari kita ambil Haskell sebagai contoh untuk kelebihan muatan. Haskell menyediakan overloading yang ditentukan pengguna melalui kelas tipe. Sebagai contoh, +dan *operasi didefinisikan dalam Numkelas tipe dan tipe apa pun yang memiliki turunan (lengkap) dari kelas tersebut dapat digunakan +. Sebagai contoh:

instance Num a => Num (b, a) where
    (x, y) + (_, y') = (x, y + y')
    -- other definitions

("Hello", 1) + ("World", 3) -- -> ("Hello", 4)

Satu hal yang terkenal tentang kelas tipe Haskell adalah Anda dapat menyingkirkannya . Yaitu Anda dapat menerjemahkan program apa pun yang menggunakan kelas tipe di program yang setara yang tidak menggunakannya.

Terjemahannya cukup sederhana:

  • Diberi definisi kelas:

    class (P_1 a, ..., P_n a) => X a where
        op_1 :: t_1   ... op_m :: t_m
    

    Anda bisa menerjemahkannya ke tipe data aljabar:

    data X a = X {
        X_P_1 :: P_1 a, ... X_P_n :: P_n a,
        X_op_1 :: t_1, ..., X_op_m :: t_m
    }
    

    Di sini X_P_idan X_op_imerupakan pemilih . Yaitu diberi nilai tipe yang X amenerapkan X_P_1nilai akan mengembalikan nilai yang disimpan di bidang itu, sehingga mereka berfungsi dengan tipe X a -> P_i a(atau X a -> t_i).

    Untuk anologi yang sangat kasar, Anda bisa memikirkan nilai untuk tipe X asebagai structs dan kemudian jika xadalah tipe X aekspresi:

    X_P_1 x
    X_op_1 x
    

    dapat dilihat sebagai:

    x.X_P_1
    x.X_op_1
    

    (Sangat mudah untuk menggunakan hanya bidang posisi daripada bidang bernama, tetapi bidang bernama lebih mudah ditangani dalam contoh dan menghindari beberapa kode pelat-ketel).

  • Diberikan contoh deklarasi:

    instance (C_1 a_1, ..., C_n a_n) => X (T a_1 ... a_n) where
        op_1 = ...; ...;  op_m = ...
    

    Anda bisa menerjemahkannya ke dalam fungsi yang memberikan kamus untuk C_1 a_1, ..., C_n a_nkelas mengembalikan nilai kamus (yaitu nilai jenis X a) untuk jenis T a_1 ... a_n.

    Dengan kata lain instance di atas dapat diterjemahkan ke fungsi seperti:

    f :: C_1 a_1 -> ... -> C_n a_n -> X (T a_1 ... a_n)
    

    (Perhatikan itu nmungkin 0).

    Dan sebenarnya kita dapat mendefinisikannya sebagai:

    f c1 ... cN = X {X_P_1=get_P_1_T, X_P_n=get_P_n_T,
                     X_op_1=op_1, ..., X_op_m=op_m}
        where
            op_1 = ...
            ...
            op_m = ...
    

    di mana op_1 = ...untuk op_m = ...adalah definisi yang ditemukan di instancedeklarasi dan get_P_i_Tadalah fungsi yang didefinisikan oleh P_icontoh dari Tjenis (ini harus ada karena P_is adalah superclasses dari X).

  • Diberi panggilan ke fungsi kelebihan beban:

    add :: Num a => a -> a -> a
    add x y = x + y
    

    Kami dapat secara eksplisit melewati kamus relatif terhadap batasan kelas dan mendapatkan panggilan yang setara:

    add :: Num a -> a -> a -> a
    add dictNum x y = ((+) dictNum) x y
    

    Perhatikan bagaimana batasan kelas hanya menjadi argumen baru. Program +yang diterjemahkan adalah pemilih seperti yang dijelaskan sebelumnya. Dengan kata lain addfungsi yang diterjemahkan , diberikan kamus untuk jenis argumennya pertama-tama akan "membongkar" fungsi aktual untuk menghitung hasil menggunakan (+) dictNumdan kemudian akan menerapkan fungsi ini ke argumen.

Ini hanya sketsa yang sangat cepat tentang semuanya. Jika Anda tertarik, Anda harus membaca artikel Simon Peyton Jones et al.

Saya percaya pendekatan serupa dapat digunakan untuk kelebihan dalam bahasa lain juga.

Namun ini menunjukkan bahwa, jika definisi Anda tentang gula sintaksis adalah (1), maka kelebihannya adalah gula sintaksis . Karena Anda dapat menyingkirkannya.

Namun program yang diterjemahkan kehilangan beberapa informasi tentang program yang asli. Sebagai contoh itu tidak menegaskan bahwa instance untuk kelas induk ada. (Meskipun operasi untuk mengekstrak kamus induk harus masih dari jenis itu, Anda bisa undefinedmemasukkan atau nilai-nilai polimorfik lainnya sehingga Anda dapat membangun nilai untuk X ytanpa membangun nilai-nilai untuk P_i y, sehingga terjemahan tidak kehilangan semua jenis keamanannya). Karenanya itu bukan gula sintaksis menurut (2)

Adapun (3). Saya tidak tahu apakah jawabannya harus ya atau tidak.

Saya akan mengatakan tidak karena, misalnya, deklarasi instance menjadi definisi fungsi. Fungsi yang kelebihan beban mendapatkan parameter baru (yang berarti ia mengubah definisi dan semua panggilan).

Saya akan mengatakan ya karena kedua program masih memetakan satu-ke-satu, sehingga "struktur" sebenarnya tidak banyak berubah.


Ini mengatakan, saya akan mengatakan bahwa keuntungan pragmatis yang diperkenalkan oleh kelebihan beban sangat besar sehingga menggunakan istilah "merendahkan" seperti "gula sintaksis" tampaknya tidak benar.

Anda dapat menerjemahkan semua sintaksis Haskell ke bahasa Core yang sangat sederhana (yang sebenarnya dilakukan saat kompilasi), sehingga sebagian besar sintaksis Haskell dapat dilihat sebagai "gula sintaksis" untuk sesuatu yang hanya lambda-kalkulus plus sedikit konstruksi baru. Namun kami dapat menyetujui bahwa program-program Haskell lebih mudah untuk ditangani dan sangat ringkas, sedangkan program-program yang diterjemahkan lebih sulit untuk dibaca atau dipikirkan.

Bakuriu
sumber
2

Jika pengiriman diselesaikan pada waktu kompilasi, hanya bergantung pada jenis statis dari argumen argumen, maka Anda tentu dapat berpendapat bahwa itu "gula sintaksis" menggantikan dua metode yang berbeda dengan nama yang berbeda, asalkan programmer "tahu" tipe statis dan hanya bisa menggunakan nama metode yang tepat di tempat nama yang kelebihan beban. Ini juga merupakan bentuk polimorfisme statis, tetapi dalam bentuk terbatas itu biasanya tidak terlalu kuat.

Tentu saja itu akan menjadi gangguan untuk harus mengubah nama metode yang Anda panggil setiap kali Anda mengubah jenis variabel, tetapi misalnya dalam bahasa C itu dianggap gangguan yang dapat dikelola, sehingga C tidak memiliki fungsi yang berlebihan (walaupun sekarang memiliki macro generik).

Dalam templat C ++, dan dalam bahasa apa pun yang melakukan deduksi tipe statis non-sepele, Anda tidak dapat benar-benar berdebat bahwa ini adalah "gula sintaksis" kecuali Anda juga berpendapat bahwa deduksi tipe statis adalah "gula sintaksis". Ini akan menjadi gangguan untuk tidak memiliki template, dan dalam konteks C ++ itu akan menjadi "gangguan yang tidak dapat dikelola", karena mereka sangat idiomatis dengan bahasa dan perpustakaan standarnya. Jadi dalam C ++ ini lebih dari sekadar pembantu yang bagus, sangat penting untuk gaya bahasa, dan jadi saya pikir Anda harus menyebutnya lebih dari "gula sintaksis".

Di Jawa Anda bisa mempertimbangkan lebih dari sekedar kenyamanan mempertimbangkan misalnya berapa banyak overloads ada dari PrintStream.printdan PrintStream.println. Tetapi kemudian, ada banyak DataInputStream.readXmetode karena Java tidak kelebihan pada tipe kembali, jadi dalam beberapa hal itu hanya untuk kenyamanan. Itu semua untuk tipe primitif.

Saya tidak ingat apa yang terjadi di Jawa jika saya memiliki kelas Adan Bmemperluas O, saya membebani metode foo(O), foo(A)dan foo(B), kemudian dalam generik dengan <T extends O>saya sebut di foo(t)mana tadalah contoh T. Dalam kasus di mana Tadalah Asaya mendapatkan pengiriman berdasarkan kelebihan atau itu seolah-olah aku menelepon foo(O)?

Jika yang pertama, maka kelebihan metode Java lebih baik daripada gula dengan cara yang sama seperti kelebihan C ++. Menggunakan definisi Anda, saya kira di Jawa saya bisa secara lokal menulis serangkaian pemeriksaan tipe (yang akan rapuh, karena kelebihan baru fooakan memerlukan pemeriksaan tambahan). Selain menerima kerapuhan itu saya tidak bisa membuat perubahan lokal di situs panggilan untuk memperbaikinya, sebaliknya saya harus menyerah menulis kode generik. Saya berpendapat bahwa mencegah kode kembung mungkin gula sintaksis, tetapi mencegah kode rapuh lebih dari itu. Karena itu, polimorfisme statis pada umumnya lebih dari sekadar gula sintaksis. Situasi dalam bahasa tertentu mungkin berbeda, tergantung seberapa jauh bahasa tersebut memungkinkan Anda untuk mendapatkan "tidak tahu" jenis statis.

Steve Jessop
sumber
Di Jawa, kelebihan beban diselesaikan pada waktu kompilasi. Mengingat penggunaan penghapusan tipe, tidak mungkin bagi mereka untuk sebaliknya. Selanjutnya, bahkan tanpa penghapusan jenis, jika T:Animalini adalah jenis SiameseCatdan overloads yang ada Cat Foo(Animal), SiameseCat Foo(Cat)dan Animal Foo(SiameseCat)yang berlebihan harus dipilih jika Tini SiameseCat?
supercat
@supercat: masuk akal. Jadi saya bisa menemukan jawabannya tanpa mengingat (atau, tentu saja, jalankan). Oleh karena itu, kelebihan Java tidak lebih baik daripada gula dengan cara yang sama kelebihan C ++ berkaitan dengan kode generik. Masih mungkin ada beberapa cara lain di mana mereka lebih baik dari sekedar transformasi lokal. Saya ingin tahu apakah saya harus mengubah contoh saya menjadi C ++, atau membiarkannya sebagai Java-imagined-Java-that-not-real-Java.
Steve Jessop
Kelebihan bisa berguna dalam kasus di mana metode memiliki argumen opsional, tetapi mereka juga bisa berbahaya. Misalkan garis long foo=Math.round(bar*1.0001)*5diubah menjadi long foo=Math.round(bar)*5. Bagaimana hal itu mempengaruhi semantik jika barsama dengan, misalnya, 123456789L?
supercat
@supercat Saya berpendapat bahaya nyata ada konversi implisit dari longke double.
Doval
@Doval: Kepada double?
supercat
1

Sepertinya "gula sintaksis" terdengar menghina, seperti tidak berguna atau sembrono. Itu sebabnya pertanyaan itu memicu banyak jawaban negatif.

Tapi Anda benar, metode overloading tidak menambahkan fitur apa pun ke bahasa kecuali kemungkinan untuk menggunakan nama yang sama untuk metode yang berbeda. Anda dapat membuat tipe parameter eksplisit, program akan tetap bekerja sama.

Hal yang sama berlaku untuk nama paket. String hanyalah gula sintaksis untuk java.lang.String.

Bahkan, metode seperti

void fun(int i, String c);

di kelas MyClass harus disebut sesuatu seperti "my_package_MyClass_fun_int_java_lang_String". Ini akan mengidentifikasi metode secara unik. (JVM melakukan sesuatu seperti itu secara internal). Tetapi Anda tidak ingin menulis itu. Itulah sebabnya kompiler akan membiarkan Anda menulis kesenangan (1, "satu") dan mengidentifikasi metode mana itu.

Namun ada satu hal yang dapat Anda lakukan dengan overloading: Jika Anda membebani metode dengan jumlah argumen yang sama, kompiler akan mencari tahu versi mana yang paling cocok dengan argumen yang diberikan oleh argumen yang cocok, tidak hanya dengan jenis yang sama, tetapi juga di mana argumen yang diberikan adalah subkelas dari argumen yang dideklarasikan.

Jika Anda memiliki dua prosedur kelebihan beban

addParameter(String name, Object value);
addParameter(String name, Date value);

Anda tidak perlu tahu bahwa ada versi spesifik dari prosedur untuk Tanggal. addParameter ("hello", "world) akan memanggil versi pertama, addParameter (" now ", Date baru ()) akan memanggil versi kedua.

Tentu saja, Anda harus menghindari kelebihan metode dengan metode lain yang melakukan hal yang sama sekali berbeda.

Florian F
sumber
1

Menariknya, jawaban untuk pertanyaan ini akan tergantung pada bahasanya.

Secara khusus, ada interaksi antara overloading dan pemrograman generik (*), dan tergantung pada bagaimana pemrograman generik diimplementasikan mungkin hanya gula sintaksis (Karat) atau benar-benar diperlukan (C ++).

Yaitu, ketika pemrograman generik diimplementasikan dengan antarmuka eksplisit (dalam Rust atau Haskell, mereka akan menjadi kelas tipe), maka overloading hanyalah gula sintaksis; atau bahkan mungkin bukan bagian dari bahasa.

Di sisi lain, ketika pemrograman generik diimplementasikan dengan mengetik bebek (baik itu dinamis atau statis) maka nama metode ini merupakan kontrak yang penting, dan oleh karena itu kelebihan beban adalah wajib bagi sistem untuk bekerja.

(*) Digunakan dalam arti menulis metode sekali, untuk mengoperasikan berbagai jenis dengan cara yang seragam.

Matthieu M.
sumber
0

Dalam beberapa bahasa tidak diragukan lagi hanya gula sintaksis. Namun, apa itu gula tergantung pada sudut pandang Anda. Saya akan meninggalkan diskusi ini untuk nanti dalam jawaban ini.

Untuk saat ini saya hanya ingin mencatat bahwa dalam beberapa bahasa itu tentu saja bukan gula sintaksis. Setidaknya tidak tanpa mengharuskan Anda untuk menggunakan logika / algoritma yang sama sekali berbeda untuk mengimplementasikan hal yang sama. Ini seperti mengklaim rekursi adalah gula sintaksis (yang karena Anda dapat menulis semua algoritma rekursif dengan loop dan tumpukan).

Salah satu contoh penggunaan yang sangat sulit untuk diganti berasal dari bahasa yang ironisnya tidak menyebut fitur ini "function overloading". Sebaliknya itu disebut "pencocokan pola" (yang dapat dilihat sebagai superset dari overloading karena kita dapat membebani bukan hanya tipe tetapi juga nilai).

Inilah implementasi klasik naif dari fungsi Fibonacci di Haskell:

fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

Tiga fungsi tersebut bisa diganti dengan if / else seperti yang biasa dilakukan dalam bahasa lain. Tapi itu pada dasarnya membuat definisi yang sangat sederhana:

fib n = fib (n-1) + fib (n-2)

jauh lebih berantakan dan tidak secara langsung mengekspresikan gagasan matematika dari deret Fibonacci.

Jadi kadang-kadang bisa berupa sintaksis gula jika hanya digunakan untuk memungkinkan Anda memanggil fungsi dengan argumen yang berbeda. Tetapi terkadang itu jauh lebih mendasar dari itu.


Sekarang untuk kebijaksanaan apa kelebihan operator mungkin menjadi gula. Anda telah mengidentifikasi satu use case - dapat digunakan untuk mengimplementasikan fungsi serupa yang menggunakan argumen berbeda. Begitu:

function print (string x) { stdout.print(x) };
function print (number x) { stdout.print(x.toString) };

dapat juga diimplementasikan sebagai:

function printString (string x) {...}
function printNumber (number x) {...}

atau bahkan:

function print (auto x) {
    if (x instanceof String) {...}
    if (x instanceof Number) {...}
}

Tetapi overloading operator juga bisa menjadi gula untuk mengimplementasikan argumen opsional (beberapa bahasa memiliki overloading operator tetapi bukan argumen opsional):

function print (string x) {...}
function print (string x, stream io) {...}

dapat digunakan untuk mengimplementasikan:

function print (string x, stream io=stdout) {...}

Dalam bahasa seperti itu (google "Bahasa Ferit") menghapus overloading operator secara drastis menghapus satu fitur - argumen opsional. Diberikan dalam bahasa dengan kedua fitur (c ++) menghapus satu atau yang lain tidak akan memiliki efek bersih karena keduanya dapat digunakan untuk mengimplementasikan argumen opsional.

Slebetman
sumber
Haskell adalah contoh yang baik mengapa operator overload bukan gula sintaksis, tapi saya pikir contoh yang lebih baik adalah mendekonstruksi tipe data aljabar dengan pencocokan pola (sesuatu yang sejauh yang saya tahu mustahil tanpa pencocokan pola).
11684
@ 11684: Bisakah Anda menunjukkan sebuah contoh? Jujur saya tidak tahu sama sekali Haskell tetapi menemukan polanya cocok dengan anggun ketika saya melihat contoh itu (di computerphile di youtube).
slebetman
Diberikan tipe data seperti data PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfoAnda dapat mencocokkan pola pada konstruktor tipe.
11684
Seperti ini: getPayment :: PaymentInfo -> a getPayment CashOnDelivery = error "Should have been paid already" getPayment (Adress addr) = -- code to notify administration to send a bill getPayment (UserInvoice cust) = --whatever. I took the data type from a Haskell tutorial and have no idea what an invoice is. Saya harap komentar ini bisa dimengerti.
11684
0

Saya pikir ini adalah gula sintaksis sederhana dalam sebagian besar bahasa (setidaknya yang saya tahu ...) karena mereka semua memerlukan Function-call yang tidak diragukan pada saat kompilasi. Dan kompiler hanya mengganti panggilan fungsi dengan pointer eksplisit ke tanda tangan implementasi yang tepat.

Contoh di Jawa:

String s; int i;
mangle(s);  // Translates to CALL ('mangle':LString):(s)
mangle(i);  // Translates to CALL ('mangle':Lint):(i)

Jadi pada akhirnya itu bisa sepenuhnya digantikan oleh kompiler-makro sederhana dengan pencarian dan ganti, mengganti fungsi mangle yang kelebihan beban dengan mangle_String dan mangle_int - karena argumen-daftar adalah bagian dari pengenal fungsi akhirnya ini praktis apa yang terjadi -> dan oleh karena itu hanya gula sintaksis.

Sekarang jika ada bahasa, di mana fungsi benar-benar ditentukan saat runtime, seperti dengan Metode yang ditimpa dalam objek, ini akan berbeda. Tapi saya tidak berpikir ada bahasa seperti itu, karena method.overloading cenderung ambiguitas, yang tidak dapat diselesaikan oleh kompiler dan yang harus ditangani oleh programmer dengan pemeran eksplisit. Ini tidak dapat dilakukan saat runtime.

Falco
sumber
0

Di Java, jenis informasi dikompilasi dan yang kelebihannya dipanggil ditentukan pada waktu kompilasi.

Berikut ini cuplikan dari sun.misc.Unsafe (utilitas untuk Atomics) seperti yang terlihat di editor file kelas Eclipse.

  // Method descriptor #143 (Ljava/lang/Object;I)I (deprecated)
  // Stack: 4, Locals: 3
  @java.lang.Deprecated
  public int getInt(java.lang.Object arg0, int arg1);
    0  aload_0 [this]
    1  aload_1 [arg0]
    2  iload_2 [arg1]
    3  i2l
    4  invokevirtual sun.misc.Unsafe.getInt(java.lang.Object, long) : int [231]
    7  ireturn
      Line numbers:
        [pc: 0, line: 213]

karena Anda dapat melihat informasi jenis metode yang dipanggil (saluran 4) termasuk dalam panggilan.

Ini berarti bahwa Anda dapat membuat kompiler java yang mengambil informasi jenis. Misalnya menggunakan notasi seperti itu sumber di atas akan menjadi:

@Deprecated
public in getInt(Object arg0, int arg1){
     return getInt$(Object,long)(arg0, arg1);
}

dan para pemain lama akan opsional.

Dalam bahasa-bahasa yang dikompilasi secara statis-diketik lainnya Anda akan melihat setup yang sama di mana kompiler akan memutuskan mana kelebihan akan dipanggil tergantung pada jenis dan memasukkannya dalam mengikat / panggilan.

Pengecualiannya adalah pustaka dinamis C di mana informasi jenis tidak termasuk dan mencoba untuk membuat fungsi kelebihan akan menyebabkan linker mengeluh.

ratchet freak
sumber