วิธีการมากไปกว่าน้ำตาล syntactic? [ปิด]


19

วิธีการบรรทุกเกินพิกัดชนิดของความหลากหลาย? สำหรับฉันดูเหมือนว่าการแยกความแตกต่างของวิธีการที่มีชื่อเดียวกันและพารามิเตอร์ที่แตกต่างกัน ดังนั้นstuff(Thing t)และstuff(Thing t, int n)เป็นวิธีการที่แตกต่างกันอย่างสิ้นเชิงเท่าที่รวบรวมและรันไทม์มีความกังวล

มันสร้างภาพลวงตาบนฝั่งของผู้โทรว่ามันเป็นวิธีเดียวกันกับที่ทำหน้าที่แตกต่างกันในวัตถุชนิดต่าง ๆ - polymorphism แต่นั่นเป็นเพียงภาพลวงตาเพราะจริงstuff(Thing t)และstuff(Thing t, int n)วิธีการที่แตกต่างกันอย่างสิ้นเชิง

วิธีการมากไปกว่าน้ำตาล syntactic? ฉันพลาดอะไรไปรึเปล่า?


ความหมายทั่วไปสำหรับน้ำตาลประโยคคือว่ามันเป็นท้องถิ่นอย่างหมดจด ความหมายการเปลี่ยนชิ้นส่วนของรหัสให้เทียบเท่า 'หวาน' หรือในทางกลับกันเกี่ยวข้องกับการเปลี่ยนแปลงในท้องถิ่นที่ไม่ส่งผลกระทบต่อโครงสร้างโดยรวมของโปรแกรม และฉันคิดว่าวิธีการบรรทุกเกินพิกัดเหมาะสมกับเกณฑ์นี้อย่างแม่นยำ ลองดูตัวอย่างเพื่อสาธิต:

พิจารณาชั้นเรียน:

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

พิจารณาคลาสอื่นที่ใช้คลาสนี้:

/* 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
    }
}

ตกลง. ทีนี้มาดูกันว่าต้องเปลี่ยนอะไรถ้าเราแทนที่ method overloading ด้วย method ปกติ:

readวิธีการในReaderการเปลี่ยนแปลงและreadBook(Book) readFile(file)เพียงเรื่องของการเปลี่ยนชื่อ

รหัสการโทรในFileProcessorการเปลี่ยนแปลงเล็กน้อย: การเปลี่ยนแปลงreader.read(file)reader.readFile(file)

และนั่นคือมัน

ในขณะที่คุณสามารถมองเห็นความแตกต่างระหว่างการใช้วิธีการบรรทุกเกินพิกัดและไม่ได้ใช้มันที่มีในท้องถิ่นได้อย่างหมดจด และนั่นคือเหตุผลที่ฉันคิดว่ามันมีคุณสมบัติเป็นน้ำตาลซินแทกติกที่บริสุทธิ์

ฉันต้องการฟังคำคัดค้านของคุณหากคุณมีบางอย่างบางทีฉันอาจขาดอะไรบางอย่าง


48
ในที่สุดคุณสมบัติการเขียนโปรแกรมภาษาใด ๆ ที่เป็นเพียงน้ำตาล syntactic สำหรับผู้ประกอบดิบ
ฟิลิปป์

31
@Philipp: ขออภัย แต่นั่นเป็นคำสั่งที่โง่จริงๆ ภาษาการเขียนโปรแกรมได้รับประโยชน์จากความหมายไม่ใช่ไวยากรณ์ คุณสมบัติเช่นระบบการพิมพ์สามารถให้การค้ำประกันที่เกิดขึ้นจริงถึงแม้ว่าพวกเขาจริงอาจทำให้คุณต้องเขียนมากขึ้น
back2dos

3
ถามตัวคุณเองว่า: ผู้ปฏิบัติงานบรรทุกน้ำตาลมากเกินไปหรือไม่ ไม่ว่าคำตอบของคำถามที่คุณถืออยู่นั้นเป็นคำตอบของคำถามที่คุณถาม;)
back2dos

5
@ back2dos: เห็นด้วยกับคุณโดยสิ้นเชิง ฉันอ่านประโยคว่า "ทุกอย่างเป็นแค่น้ำตาลประโยคสำหรับผู้ประกอบ" บ่อยเกินไปและมันผิดอย่างชัดเจน น้ำตาลซินแทคติคเป็นทางเลือก (อาจดีกว่า) สำหรับซินแท็กซ์ที่มีอยู่บางตัวที่ไม่ได้เพิ่มซีแมนทิกส์ใหม่ ๆ
Giorgio

6
@Giorgio: ถูกต้อง! มีคำจำกัดความที่ชัดเจนในกระดาษสถานที่สำคัญของ Matthias Felleisen เกี่ยวกับการแสดงออก โดยทั่วไป: น้ำตาล syntactic เป็นท้องถิ่นอย่างหมดจด หากคุณต้องเปลี่ยนโครงสร้างส่วนกลางของโปรแกรมเพื่อลบการใช้งานคุณสมบัติทางภาษานั่นไม่ใช่น้ำตาลเชิงประโยค คือการเขียนรหัส OO polymorphic ในประกอบมักจะเกี่ยวข้องกับการเพิ่มตรรกะการจัดส่งทั่วโลกซึ่งเป็นไม่ได้ในท้องถิ่นอย่างหมดจดจึง OO เป็นไม่ได้ "เพียงน้ำตาลประโยคสำหรับประกอบ"
Jörg W Mittag

คำตอบ:


29

ในการตอบคำถามนี้ก่อนอื่นคุณต้องมีคำจำกัดความของคำว่า ฉันจะไปกับWikipedia's :

ในวิทยาการคอมพิวเตอร์น้ำตาล syntactic เป็นไวยากรณ์ในภาษาการเขียนโปรแกรมที่ออกแบบมาเพื่อให้สิ่งที่ง่ายต่อการอ่านหรือการแสดง มันทำให้ภาษา "หวาน" สำหรับการใช้งานของมนุษย์: สิ่งที่สามารถแสดงออกได้ชัดเจนยิ่งขึ้นรัดกุมหรือในรูปแบบทางเลือกที่บางคนอาจชอบ

[ ... ]

โดยเฉพาะอย่างยิ่งการสร้างในภาษาที่เรียกว่าน้ำตาลทรายถ้ามันสามารถลบออกจากภาษาโดยไม่มีผลกระทบใด ๆ กับสิ่งที่ภาษาสามารถทำได้

ดังนั้นภายใต้คำนิยามนี้ฟีเจอร์เช่น varargs ของ Java หรือ Scala's for-comprehension นั้นคือ syntactic sugar: แปลเป็นภาษาพื้นฐาน (อาเรย์ในกรณีแรกเรียกไปที่ map / flatmap / filter ในครั้งที่สอง) ไม่เปลี่ยนสิ่งที่คุณสามารถทำได้กับภาษา

อย่างไรก็ตามวิธีการใช้งานมากไปนั้นไม่ใช่น้ำตาลวากยสัมพันธ์ภายใต้คำจำกัดความนี้เนื่องจากการลบออกจะทำให้ภาษาเปลี่ยนไปโดยพื้นฐาน

จริงคุณสามารถจำลองวิธีการมากไปตราบเท่าที่คุณมีวิธีเข้าถึงข้อโต้แย้งของวิธีการและสามารถใช้โครงสร้าง "ถ้า" ตามอาร์กิวเมนต์ที่คุณได้รับ แต่ถ้าคุณคิดว่าน้ำตาลซินแทคติคคุณต้องพิจารณาอะไรที่อยู่เหนือเครื่องทัวริงเพื่อให้เป็นน้ำตาลซินแทกติก


22
การลบการโหลดมากเกินไปจะไม่เปลี่ยนสิ่งที่ภาษาสามารถทำได้ คุณยังสามารถทำสิ่งเดียวกันได้เหมือนเมื่อก่อน คุณเพียงแค่ต้องเปลี่ยนชื่อบางวิธี นั่นเป็นการเปลี่ยนแปลงเล็กน้อยมากกว่าการวนลูปแบบซ้ำ ๆ
Doval

9
ดังที่ฉันได้กล่าวไปแล้วคุณสามารถใช้วิธีการที่ทุกภาษา (รวมถึงภาษาเครื่อง) เป็นเพียงประโยคน้ำตาลที่อยู่ด้านบนของเครื่องทัวริง
kdgregory

9
ที่ผมเห็นมันวิธีการบรรทุกเกินพิกัดเพียงแค่ช่วยให้คุณทำsum(numbersArray)และsum(numbersList)แทนและsumArray(numbersArray) sumList(numbersList)ฉันเห็นด้วยกับ Doval ดูเหมือนว่าจะเป็นเพียงน้ำตาลทราย
Aviv Cohn

3
ภาษาส่วนใหญ่ ลองดำเนินการinstanceof, เรียน, มรดก, อินเตอร์เฟซ, generics สะท้อนหรือ specifiers เข้าถึงโดยใช้if, whileและผู้ประกอบการบูลีนที่มีความหมายเดียวกันแน่นอน ไม่มีกรณีมุม โปรดทราบว่าฉันไม่ได้ท้าทายให้คุณคำนวณสิ่งเดียวกันกับการใช้งานเฉพาะของสิ่งก่อสร้างเหล่านั้น ฉันรู้อยู่แล้วว่าคุณสามารถคำนวณอะไรก็ได้โดยใช้ตรรกะบูลีนและการแยกย่อย / วนซ้ำ ฉันขอให้คุณใช้สำเนาที่สมบูรณ์แบบของความหมายของฟีเจอร์ภาษาเหล่านั้นรวมถึงการรับประกันแบบคงที่ใด ๆ ที่พวกเขาให้ (การตรวจสอบเวลาคอมไพล์จะต้องทำในเวลารวบรวม)
Doval

6
@Doval, kdgregory: เพื่อที่จะกำหนดน้ำตาล syntactic คุณต้องกำหนดมันสัมพันธ์กับความหมายบางอย่าง หากความหมายเดียวที่คุณมีคือ "โปรแกรมนี้คำนวณอะไร?" ก็เป็นที่ชัดเจนว่าทุกอย่างเป็นเพียงน้ำตาลซินแทติกสำหรับเครื่องทัวริง ในทางตรงกันข้ามหากคุณมีความหมายที่คุณสามารถพูดเกี่ยวกับวัตถุและการดำเนินการบางอย่างกับพวกเขาแล้วการลบไวยากรณ์บางอย่างจะไม่อนุญาตให้คุณแสดงการดำเนินการเหล่านั้นอีกต่อไปแม้ว่าภาษายังคงเป็นทัวริงที่สมบูรณ์
Giorgio

13

คำว่าน้ำตาลซินแทคติกโดยทั่วไปหมายถึงกรณีที่คุณสมบัติถูกกำหนดโดยการทดแทน ภาษาไม่ได้กำหนดว่าคุณลักษณะจะทำอะไร แต่จะกำหนดว่าเทียบเท่ากับสิ่งอื่นใด ตัวอย่างเช่นสำหรับแต่ละลูป

for(Object alpha: alphas) {
}

กลายเป็น:

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

หรือรับฟังก์ชั่นที่มีอาร์กิวเมนต์ของตัวแปร

void foo(int... args);

foo(3, 4, 5);

ซึ่งกลายเป็น:

void Foo(int[] args);

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

ดังนั้นจึงมีการทดแทนเล็กน้อยของไวยากรณ์ในการใช้คุณสมบัติในแง่ของคุณสมบัติอื่น ๆ

ลองดูวิธีการมากไป

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

foo(4.5);

สิ่งนี้สามารถเขียนใหม่เป็น:

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

foo_double(4.5);

แต่มันไม่เทียบเท่ากับที่ ภายในโมเดลของ Java สิ่งนี้แตกต่างกัน foo(int a)ไม่ใช้foo_intฟังก์ชั่นที่จะสร้าง Java ไม่ได้ใช้วิธีการบรรทุกเกินพิกัดโดยให้ชื่อตลกฟังก์ชั่นที่ไม่ชัดเจน หากต้องการนับเป็นน้ำตาลเชิงไวยากรณ์จาวาจะต้องแกล้งทำเป็นว่าคุณเขียนfoo_intและfoo_doubleฟังก์ชั่นจริงๆแต่ก็ไม่ได้


2
ฉันไม่คิดว่าจะมีใครเคยพูดว่าการเปลี่ยนแปลงของซินแท็กซ์น้ำตาลนั้นจะไม่สำคัญ แม้ว่ามันจะเป็นเช่นนั้นก็ตามฉันก็พบว่าการอ้างสิทธิ์But, the transformation isn't trivial. At the least, you have to determine the types of the parameters.นั้นไม่สมบูรณ์เพราะไม่จำเป็นต้องกำหนดประเภท ; พวกเขารู้จักกันในเวลารวบรวม
Doval

3
"หากต้องการนับว่าเป็นน้ำตาลเชิงไวยากรณ์จาวาจะต้องแกล้งทำเป็นว่าคุณเขียนฟังก์ชั่น foo_int และ foo_double จริง ๆ แต่ก็ไม่ได้" - ตราบใดที่เราพูดถึงวิธีการบรรทุกเกินพิกัดและไม่แตกต่างกันจะมีความแตกต่างอะไรระหว่างfoo(int)/ foo(double)และfoo_int/ foo_double? ฉันไม่รู้จัก Java ดีนัก แต่ฉันคิดว่าการเปลี่ยนชื่อดังกล่าวเกิดขึ้นจริงใน JVM (อาจจะใช้foo(args)แทนfoo_args- อย่างน้อยก็ใน C ++ ที่มีสัญลักษณ์ mangling (ok - สัญลักษณ์ mangling เป็นเทคนิคในรายละเอียดการใช้งานและไม่ใช่ส่วนหนึ่ง) ของภาษา).
Maciej Piechotka

2
@Doval: "ฉันไม่คิดว่ามีใครเคยพูดว่าการเปลี่ยนแปลงสำหรับน้ำตาลไวยากรณ์จะต้องมีเรื่องเล็กน้อย" - ทรู แต่มันจะต้องมีในท้องถิ่น ความหมายเพียงประโยชน์ของน้ำตาลประโยคที่ฉันรู้จากกระดาษที่มีชื่อเสียงแมตเธียสเฟลเลเซนใน expressivity ภาษาและโดยทั่วไปจะบอกว่าถ้าคุณสามารถเขียนโปรแกรมที่เขียนในภาษาL + Y (เช่นบางภาษาLกับบางคุณลักษณะY ) ใน ภาษาL (เช่นส่วนย่อยของภาษานั้นโดยไม่มีคุณสมบัติy ) โดยไม่ต้องเปลี่ยนโครงสร้างทั่วโลกของโปรแกรม (เช่นการเปลี่ยนแปลงในท้องถิ่นเท่านั้น) จากนั้นyคือน้ำตาล syntactic ในL + yและไม่
Jörg W Mittag

2
... ไม่ได้เพิ่มขึ้นL 's expressivity แต่ถ้าคุณไม่สามารถทำอย่างนั้นเช่นถ้าคุณจำเป็นต้องทำการเปลี่ยนแปลงโครงสร้างระดับโลกของโปรแกรมของคุณแล้วมันเป็นไม่ได้น้ำตาลประโยคและไม่ในความเป็นจริงทำให้L + Yแสดงออกมากขึ้นกว่าL ตัวอย่างเช่น Java ที่มีforลูปที่ปรับปรุงแล้วจะไม่สามารถแสดงออกได้มากกว่า Java ที่ไม่มีอยู่ (มันดีกว่า, กระชับกว่า, อ่านได้มากขึ้น, และดีกว่าทั้งหมด, ฉันจะเถียง แต่ไม่แสดงออกมากขึ้น) ฉันไม่แน่ใจเกี่ยวกับกรณีการบรรทุกเกินพิกัด ฉันอาจจะต้องอ่านกระดาษอีกครั้งเพื่อให้แน่ใจ ไส้ของฉันบอกว่ามันเป็นน้ำตาลทราย แต่ฉันไม่แน่ใจ
Jörg W Mittag

2
@MaciejPiechotka ถ้ามันเป็นส่วนหนึ่งของคำนิยามภาษาที่ฟังก์ชั่นถูกเปลี่ยนชื่อและคุณสามารถเข้าถึงฟังก์ชั่นภายใต้ชื่อเหล่านั้นฉันคิดว่ามันจะเป็นน้ำตาล syntactic แต่เนื่องจากมันถูกซ่อนไว้เป็นรายละเอียดการปฏิบัติฉันคิดว่ามันตัดสิทธิ์จากการเป็นน้ำตาล syntactic
Winston Ewert

8

จากชื่อของ mangling ที่ทำงานมันไม่จำเป็นต้องมีอะไรมากไปกว่าน้ำตาล syntactic

ช่วยให้ผู้โทรจินตนาการว่าเขากำลังเรียกฟังก์ชั่นเดียวกันเมื่อเขาไม่อยู่ แต่เขาสามารถรู้ชื่อจริงของการทำงานทั้งหมดของเขา เฉพาะในกรณีที่เป็นไปได้ที่จะบรรลุความหลากหลายที่ล่าช้าโดยการส่งตัวแปรที่ไม่พิมพ์ลงในฟังก์ชั่นที่พิมพ์และมีประเภทของมันที่จัดตั้งขึ้นเพื่อให้การโทรสามารถไปยังรุ่นที่เหมาะสมตามชื่อนี้จะเป็นคุณสมบัติภาษาจริง

น่าเสียดายที่ฉันไม่เคยเห็นภาษาทำเช่นนี้ เมื่อมีความกำกวมคอมไพเลอร์เหล่านี้ไม่สามารถแก้ไขได้พวกเขายืนยันว่านักเขียนจะแก้ไขให้พวกเขา


สถานที่ที่คุณกำลังมองหาเรียกว่า "การส่งหลายครั้ง" มีภาษาให้เลือกมากมายเช่น Haskell, Scala และ (ตั้งแต่ 4.0) C #
Iain Galloway

ฉันต้องการแยกพารามิเตอร์ในคลาสจากวิธีการโอเวอร์โหลดแบบตรง ในกรณี overloading วิธีตรงโปรแกรมเมอร์เขียนทุกรุ่นคอมไพเลอร์เพิ่งรู้วิธีการเลือกหนึ่ง นั่นเป็นเพียงน้ำตาล syntactic และได้รับการแก้ไขโดยชื่อ mangling ง่ายๆแม้สำหรับการจัดส่งหลาย --- ในการปรากฏตัวของพารามิเตอร์ในชั้นเรียนคอมไพเลอร์สร้างรหัสตามความจำเป็นและการเปลี่ยนแปลงนี้อย่างสมบูรณ์
Jon Jay Obermark

2
ฉันคิดว่าคุณเข้าใจผิด ยกตัวอย่างเช่นใน C # ถ้าหนึ่งของพารามิเตอร์ที่วิธีที่เป็นdynamicแล้วความละเอียดเกินเกิดขึ้นที่รันไทม์ไม่ได้อยู่ที่เวลารวบรวม นั่นคือสิ่งที่ส่งหลายและมันไม่สามารถจำลองแบบได้โดยการเปลี่ยนชื่อฟังก์ชั่น
Iain Galloway

ค่อนข้างเย็น ฉันยังคงสามารถทดสอบประเภทตัวแปรได้ดังนั้นนี่ยังเป็นฟังก์ชั่นในตัวที่วางซ้อนบนน้ำตาลซินแทติกติก มันเป็นคุณสมบัติภาษา แต่แทบจะไม่
Jon Jay Obermark

7

ขึ้นอยู่กับภาษาก็คือน้ำตาลซินแทคติคหรือไม่

ใน C ++ คุณสามารถทำสิ่งต่าง ๆ โดยใช้การโหลดมากเกินไปและเทมเพลตซึ่งไม่สามารถทำได้โดยไม่มีภาวะแทรกซ้อน (เขียนอินสแตนซ์ทั้งหมดของเทมเพลตด้วยตนเองหรือเพิ่มพารามิเตอร์เทมเพลตจำนวนมาก)

โปรดทราบว่าการจัดส่งแบบไดนามิกเป็นรูปแบบของการบรรทุกเกินพิกัดมติแบบไดนามิกในบางพารามิเตอร์ (สำหรับบางภาษาเพียงหนึ่งพิเศษนี้แต่ไม่ได้ทุกภาษาจะถูก จำกัด ให้) และฉันจะไม่เรียกรูปแบบของการบรรทุกเกินพิกัดน้ำตาลประโยคว่า


ฉันไม่แน่ใจว่าคำตอบอื่น ๆ ทำได้ดีขึ้นมากเพียงใดเมื่อไม่ถูกต้อง
Telastyn

5

สำหรับภาษาร่วมสมัยมันเป็นแค่น้ำตาลประโยค ในทางที่ไม่เชื่อเรื่องภาษาอย่างสมบูรณ์มันเป็นมากกว่านั้น

ก่อนหน้านี้คำตอบนี้ระบุไว้เพียงว่ามันเป็นมากกว่าน้ำตาลประโยค แต่ถ้าคุณจะเห็นในความคิดเห็น Falco ยกประเด็นว่ามีชิ้นส่วนของปริศนาที่ภาษาร่วมสมัยดูเหมือนจะหายไป; พวกเขาไม่ได้ผสมวิธีการมากไปกับการกำหนดแบบไดนามิกของฟังก์ชันที่จะเรียกใช้ในขั้นตอนเดียวกัน จะมีการชี้แจงในภายหลัง

นี่คือเหตุผลที่มันควรจะมากขึ้น

พิจารณาภาษาที่รองรับทั้งวิธีการโอเวอร์โหลดและตัวแปรที่ไม่ได้พิมพ์ คุณสามารถมีต้นแบบวิธีการดังต่อไปนี้:

bool someFunction(int arg);

bool someFunction(string arg);

ในบางภาษาคุณอาจถูกลาออกเพื่อทราบเวลารวบรวมซึ่งจะมีการเรียกหนึ่งในรหัสเหล่านี้ แต่ในบางภาษาจะไม่มีการพิมพ์ตัวแปรทั้งหมด (หรือพวกเขากำลังพิมพ์โดยปริยายObjectหรืออะไรก็ตาม) ดังนั้นลองนึกภาพการสร้างพจนานุกรมที่คีย์คีย์จับคู่กับค่าประเภทต่าง ๆ :

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.

ทีนี้ถ้าคุณต้องการสมัครsomeFunctionกับหมายเลขห้องเหล่านั้นล่ะ คุณเรียกสิ่งนี้ว่า:

someFunction(roomNumber[someSortOfKey]);

ถูกsomeFunction(int)เรียกหรือถูกsomeFunction(string)เรียก? ที่นี่คุณจะเห็นตัวอย่างหนึ่งซึ่งสิ่งเหล่านี้ไม่ใช่วิธีการตั้งฉากแบบสมบูรณ์โดยเฉพาะในภาษาระดับสูงกว่า ภาษาต้องคิดออก - ในระหว่างรันไทม์ - ซึ่งหนึ่งในสิ่งเหล่านี้ที่จะเรียกดังนั้นจึงยังคงต้องพิจารณาว่าสิ่งเหล่านี้เป็นอย่างน้อยวิธีเดียวกัน

ทำไมไม่ใช้แค่แม่แบบ? ทำไมไม่ใช้เพียงอาร์กิวเมนต์ที่ไม่มีการพิมพ์?

ความยืดหยุ่นและการควบคุมที่ละเอียดยิ่งขึ้น บางครั้งการใช้เทมเพลต / อาร์กิวเมนต์ที่ไม่มีการพิมพ์เป็นวิธีที่ดีกว่า แต่บางครั้งก็ใช้ไม่ได้

คุณต้องคิดเกี่ยวกับกรณีที่ตัวอย่างเช่นคุณอาจมีสองวิธีลายเซ็นที่แต่ละคนใช้intและstringเป็นข้อโต้แย้ง แต่ที่สั่งซื้อจะแตกต่างกันในแต่ละลายเซ็น คุณอาจมีเหตุผลที่ดีในการทำเช่นนี้เนื่องจากการใช้งานของลายเซ็นแต่ละครั้งอาจทำสิ่งเดียวกัน แต่ส่วนใหญ่แตกต่างกันเล็กน้อย ตัวอย่างเช่นการบันทึกอาจแตกต่างกัน หรือแม้ว่าพวกเขาจะทำสิ่งเดียวกันคุณสามารถรวบรวมข้อมูลบางอย่างโดยอัตโนมัติจากลำดับที่มีการระบุอาร์กิวเมนต์ ในทางเทคนิคคุณสามารถใช้คำสั่ง pseudo-switch เพื่อกำหนดประเภทของอาร์กิวเมนต์แต่ละตัวที่ส่งผ่านเข้ามา

ดังนั้นนี่คือตัวอย่างการฝึกการเขียนโปรแกรมที่ไม่ดีต่อไปหรือไม่?

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;
    }
}

ใช่โดยและมีขนาดใหญ่ ในตัวอย่างนี้มันสามารถป้องกันไม่ให้ใครบางคนพยายามที่จะใช้สิ่งนี้กับประเภทดั้งเดิมบางอย่างและกลับมาทำงานที่ไม่คาดคิด (ซึ่งอาจเป็นสิ่งที่ดี); แต่ลองสมมุติว่าฉันย่อรหัสข้างต้นและที่จริงแล้วคุณมีโอเวอร์โหลดสำหรับประเภทดั้งเดิมทั้งหมดรวมถึงObjects ด้วย จากนั้นโค้ดถัดไปนี้จะเหมาะสมกว่าจริงๆ:

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

แต่ถ้าคุณต้องการใช้สิ่งนี้กับints และstrings เท่านั้นและถ้าคุณต้องการให้มันกลับมาจริงโดยใช้เงื่อนไขที่ง่ายกว่าหรือซับซ้อนกว่านั้น จากนั้นคุณมีเหตุผลที่ดีในการใช้งานมากไป:

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;
    }
}

แต่เฮ้ทำไมไม่ให้ชื่อเหล่านั้นฟังก์ชั่นสองชื่อต่างกันล่ะ? คุณยังคงมีการควบคุมอย่างละเอียดเหมือนเดิมใช่ไหม?

เนื่องจากตามที่ระบุไว้ก่อนหน้าโรงแรมบางแห่งใช้ตัวเลขบางแห่งใช้ตัวอักษรและบางแห่งใช้ตัวเลขและตัวอักษรผสมกัน:

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

นี่ยังไม่ใช่รหัสที่แน่นอนเหมือนกันที่ฉันจะใช้ในชีวิตจริง แต่ควรแสดงให้เห็นถึงจุดที่ฉันกำลังทำอยู่

แต่ ...นี่คือเหตุผลว่าทำไมมันจึงไม่ได้เป็นเพียงแค่น้ำตาลในภาษาร่วมสมัย

ฟัลโกยกประเด็นในการแสดงความคิดเห็นว่าภาษาปัจจุบันโดยทั่วไปไม่ได้ผสมวิธีมากไปและการเลือกฟังก์ชั่นแบบไดนามิกในขั้นตอนเดียวกัน วิธีก่อนหน้านี้ฉันเข้าใจบางภาษาในการทำงานคือคุณสามารถโอเวอร์โหลดappearsToBeFirstFloorในตัวอย่างด้านบนและจากนั้นภาษาจะพิจารณาว่ารันไทม์เวอร์ชันใดของฟังก์ชันที่จะเรียกใช้ขึ้นอยู่กับค่ารันไทม์ของตัวแปรที่ไม่ได้พิมพ์ ความสับสนนี้เกิดจากการทำงานกับภาษา ECMA บางส่วนเช่น ActionScript 3.0 ซึ่งคุณสามารถสุ่มฟังก์ชั่นที่เรียกใช้กับรหัสบรรทัดที่รันไทม์ได้อย่างง่ายดาย

อย่างที่คุณอาจทราบแล้วว่า ActionScript 3 ไม่รองรับวิธีการโอเวอร์โหลด สำหรับ VB.NET คุณสามารถประกาศและตั้งค่าตัวแปรได้โดยไม่ต้องกำหนดชนิดอย่างชัดเจน แต่เมื่อคุณพยายามส่งตัวแปรเหล่านี้เป็นอาร์กิวเมนต์ไปยังวิธีการโอเวอร์โหลดมันยังคงไม่ต้องการอ่านค่ารันไทม์เพื่อกำหนดวิธีการโทร มันต้องการหาวิธีที่มีอาร์กิวเมนต์ชนิดObjectหรือไม่มีหรือแทนที่จะเป็นอย่างอื่นแทน ดังนั้นintเมื่อเทียบกับstringตัวอย่างข้างต้นจะไม่ทำงานในภาษาที่อย่างใดอย่างหนึ่ง C ++ มีปัญหาที่คล้ายกันเช่นเมื่อคุณใช้บางสิ่งบางอย่างเช่นตัวชี้โมฆะหรือกลไกอื่น ๆ เช่นนั้นมันยังคงต้องการให้คุณแยกแยะประเภทในเวลารวบรวม

ดังนั้นตามที่หัวข้อแรกบอกว่า ...

สำหรับภาษาร่วมสมัยมันเป็นแค่น้ำตาลประโยค ในทางที่ไม่เชื่อเรื่องภาษาอย่างสมบูรณ์มันเป็นมากกว่านั้น การใช้วิธีการมากเกินไปมีประโยชน์และเกี่ยวข้องเช่นในตัวอย่างข้างต้นอาจเป็นคุณสมบัติที่ดีในการเพิ่มภาษาที่มีอยู่ (ตามที่ได้รับการร้องขออย่างกว้างขวางสำหรับ AS3) หรืออาจเป็นหนึ่งในเสาหลักที่แตกต่างกันสำหรับ การสร้างภาษาเชิงกระบวนงาน / เชิงวัตถุใหม่


3
คุณสามารถตั้งชื่อภาษาใด ๆ ที่จัดการ Function-Dispatch ตอนรันไทม์และไม่คอมไพล์ไทม์ได้หรือไม่? ทุกภาษาที่ฉันรู้ต้องมีความแน่นอนในการคอมไพล์ซึ่งเรียกว่าฟังก์ชั่น ...
Falco

@Falco ActionScript 3.0 จัดการได้ในขณะทำงาน คุณสามารถเช่นใช้ฟังก์ชั่นที่ผลตอบแทนที่หนึ่งในสามของสายที่สุ่มและจากนั้นใช้ค่าตอบแทนในการเรียกหนึ่งในสามของฟังก์ชั่นใด ๆ ที่สุ่ม: this[chooseFunctionNameAtRandom](); หากchooseFunctionNameAtRandom()ผลตอบแทนอย่างใดอย่างหนึ่ง"punch", "kick"หรือ"dodge"แล้วคุณสามารถ thusly ใช้ง่ายมากสุ่ม องค์ประกอบเช่น AI ของศัตรูในเกมแฟลช
Panzercrisis

1
ใช่ - แต่เป็นทั้งวิธีการทางความหมายที่แท้จริงเพื่อรับฟังก์ชั่นการส่งข้อมูลแบบไดนามิก Java มีสิ่งเหล่านี้เช่นกัน แต่มันแตกต่างจากการบรรทุกเกินพิกัดการบรรทุกเกินพิกัดเป็นแบบคงที่และเพียงแค่น้ำตาลประโยคในขณะที่การแจกจ่ายและการสืบทอดแบบไดนามิกเป็นคุณสมบัติภาษาจริงซึ่งมีฟังก์ชั่นใหม่!
Falco

1
... ฉันยังลองโมฆะพอยน์เตอร์ใน C ++, เช่นเดียวกับพอยน์เตอร์คลาสเบส, แต่คอมไพเลอร์ต้องการให้ฉันแก้ปัญหาด้วยตนเองก่อนที่จะส่งไปยังฟังก์ชั่น ดังนั้นตอนนี้ฉันสงสัยว่าจะลบคำตอบนี้หรือไม่ มันเริ่มที่จะดูเหมือนว่าภาษาต่างๆมักจะเดินไปจนถึงการรวมตัวเลือกฟังก์ชั่นแบบไดนามิกเข้ากับฟังก์ชั่นการโอเวอร์โหลดในคำสั่งหรือคำสั่งเดียวกัน แต่จากนั้นก้าวออกไปในวินาทีสุดท้าย มันจะเป็นคุณสมบัติทางภาษาที่ดีแม้ว่า; อาจมีบางคนต้องการสร้างภาษาที่มีสิ่งนี้
Panzercrisis

1
ปล่อยให้คำตอบพักอยู่ลองนึกถึงงานวิจัยของคุณจากความคิดเห็นในคำตอบด้วยล่ะ?
Falco

2

มันขึ้นอยู่กับการกำหนด "น้ำตาลเชิงกล" ของคุณ ฉันจะพยายามพูดถึงคำจำกัดความบางอย่างที่อยู่ในใจฉัน:

  1. คุณลักษณะคือน้ำตาลประโยคเมื่อโปรแกรมที่ใช้สามารถแปลได้ในโปรแกรมอื่นที่ไม่ได้ใช้คุณสมบัตินี้

    ที่นี่เราสันนิษฐานว่ามีชุดของคุณลักษณะดั้งเดิมที่ไม่สามารถแปลได้: กล่าวอีกนัยหนึ่งก็คือไม่ต้องวนซ้ำ "คุณสามารถแทนที่คุณลักษณะ X โดยใช้คุณสมบัติ Y" และ "คุณสามารถแทนที่คุณสมบัติ Y ด้วยคุณสมบัติ X" หากหนึ่งในสองนั้นเป็นจริงมากกว่าทั้งสองคุณลักษณะอื่น ๆ สามารถแสดงในแง่ของคุณสมบัติที่ไม่ได้เป็นคนแรกหรือมันเป็นคุณสมบัติดั้งเดิม

  2. เช่นเดียวกับคำจำกัดความ 1 แต่ด้วยความต้องการพิเศษที่โปรแกรมที่แปลแล้วนั้นปลอดภัยต่อการใช้งานประเภทแรกเช่นโดยการลบความผิดพลาดคุณจะไม่สูญเสียข้อมูลใด ๆ

  3. คำจำกัดความของ OP: คุณลักษณะคือน้ำตาลประโยคถ้าการแปลไม่ได้เปลี่ยนโครงสร้างของโปรแกรม แต่ต้องการเพียง "การเปลี่ยนแปลงในระบบ"

ลองยก Haskell เป็นตัวอย่างสำหรับการบรรทุกเกินพิกัด Haskell จัดเตรียมการบรรทุกเกินพิกัดที่ผู้ใช้กำหนดผ่านคลาสประเภท สำหรับตัวอย่าง+และ*การดำเนินงานที่กำหนดไว้ในNumระดับประเภทและชนิดที่มี (ฉบับสมบูรณ์) +ตัวอย่างของการเรียนดังกล่าวสามารถนำมาใช้กับใด ตัวอย่างเช่น:

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

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

สิ่งหนึ่งที่เป็นที่รู้จักกันดีเกี่ยวกับคลาสประเภทของ Haskell คือคุณสามารถกำจัดมันได้ นั่นคือคุณสามารถแปลโปรแกรมใด ๆ ที่ใช้คลาสพิมพ์ในโปรแกรมที่เทียบเท่าที่ไม่ได้ใช้

การแปลค่อนข้างง่าย:

  • ให้นิยามคลาส:

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

    คุณสามารถแปลมันเป็นชนิดข้อมูลเชิงพีชคณิต:

    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
    }
    

    ที่นี่X_P_iและX_op_iมีตัวเลือก นั่นคือการกำหนดค่าประเภทที่X aใช้X_P_1กับค่าจะส่งคืนค่าที่เก็บในฟิลด์นั้นดังนั้นจึงเป็นฟังก์ชันที่มีประเภทX a -> P_i a(หรือX a -> t_i)

    สำหรับanology คร่าวๆคุณสามารถนึกค่าของประเภทX aเป็นstructs แล้วถ้าxเป็นประเภทX aของนิพจน์:

    X_P_1 x
    X_op_1 x
    

    อาจถูกมองว่าเป็น:

    x.X_P_1
    x.X_op_1
    

    (มันง่ายต่อการใช้งานเพียงแค่ตำแหน่งที่เป็นฟิลด์แทนที่จะเป็นฟิลด์ที่มีชื่อ แต่ฟิลด์ที่ตั้งชื่อนั้นจะจัดการได้ง่ายกว่าในตัวอย่างและหลีกเลี่ยงรหัสจานบางส่วน)

  • รับการประกาศตัวอย่าง:

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

    คุณสามารถแปลเป็นฟังก์ชั่นที่ได้รับพจนานุกรมสำหรับC_1 a_1, ..., C_n a_nชั้นเรียนที่ส่งกลับค่าพจนานุกรม (เช่นค่าของชนิดX a) T a_1 ... a_nสำหรับประเภท

    กล่าวอีกนัยหนึ่งตัวอย่างข้างต้นสามารถแปลเป็นฟังก์ชันเช่น:

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

    (โปรดทราบว่าnอาจเป็น0)

    และในความเป็นจริงเราสามารถนิยามมันเป็น:

    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 = ...
    

    โดยที่op_1 = ...จะop_m = ...เป็นคำจำกัดความที่พบในการinstanceประกาศและget_P_i_Tฟังก์ชั่นที่กำหนดโดยP_iอินสแตนซ์ของTประเภท (เหล่านี้จะต้องมีอยู่เพราะP_is เป็นซูเปอร์คลาสของX)

  • รับสายไปยังฟังก์ชั่นโอเวอร์โหลด:

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

    เราสามารถส่งผ่านพจนานุกรมอย่างชัดเจนเมื่อเทียบกับข้อ จำกัด ของชั้นเรียนและรับสายที่เทียบเท่า:

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

    สังเกตว่าข้อ จำกัด ของคลาสกลายเป็นอาร์กิวเมนต์ใหม่ได้อย่างไร +ในโปรแกรมแปลเป็นตัวเลือกตามที่อธิบายไว้ก่อน ในคำอื่น ๆaddฟังก์ชั่นที่แปลให้พจนานุกรมสำหรับประเภทของการโต้แย้งของมันจะเป็นครั้งแรก "แกะ" ฟังก์ชั่นที่เกิดขึ้นจริงเพื่อคำนวณผลการใช้(+) dictNumแล้วจะใช้ฟังก์ชั่นนี้กับข้อโต้แย้ง

นี่เป็นเพียงภาพร่างที่รวดเร็วมากเกี่ยวกับเรื่องทั้งหมด หากคุณสนใจคุณควรอ่านบทความของ Simon Peyton Jones และคณะ

ฉันเชื่อว่าวิธีการที่คล้ายกันนี้สามารถใช้สำหรับการโหลดมากเกินไปในภาษาอื่นได้

แต่นี้แสดงให้เห็นว่าถ้าความหมายของน้ำตาลประโยคคือ (1) จากนั้นมากไปเป็นน้ำตาลประโยค เพราะคุณสามารถกำจัดมันได้

อย่างไรก็ตามโปรแกรมแปลแปลข้อมูลบางอย่างเกี่ยวกับโปรแกรมต้นฉบับ ตัวอย่างเช่นมันไม่ได้บังคับใช้ว่ามีอินสแตนซ์สำหรับคลาสพาเรนต์อยู่ (แม้ว่าการดำเนินการเพื่อแยกพจนานุกรมของผู้ปกครองจะต้องยังคงเป็นประเภทนั้นคุณสามารถส่งผ่านundefinedหรือค่า polymorphic อื่น ๆ เพื่อให้คุณสามารถสร้างมูลค่าได้X yโดยไม่ต้องสร้างค่าสำหรับP_i yดังนั้นการแปลไม่หลวม ความปลอดภัยของประเภท) ดังนั้นมันจึงไม่ใช่น้ำตาลซินแทคติตาม (2)

สำหรับ (3) ฉันไม่รู้ว่าคำตอบควรเป็นใช่หรือไม่

ฉันจะบอกว่าไม่ได้เพราะตัวอย่างเช่นการประกาศอินสแตนซ์กลายเป็นนิยามของฟังก์ชัน ฟังก์ชั่นที่โอเวอร์โหลดรับพารามิเตอร์ใหม่ (ซึ่งหมายความว่ามันจะเปลี่ยนทั้งความหมายและการโทรทั้งหมด)

ฉันจะบอกว่าใช่เพราะทั้งสองโปรแกรมยังคงทำแผนที่แบบหนึ่งต่อหนึ่งดังนั้น "โครงสร้าง" จึงไม่ได้เปลี่ยนแปลงอะไรมากมาย


สิ่งนี้กล่าวว่าฉันจะบอกว่าประโยชน์ในทางปฏิบัติที่นำมาใช้โดยการบรรทุกเกินพิกัดนั้นใหญ่มากจนใช้คำว่า "เสื่อมเสีย" เช่น "น้ำตาลเชิงซ้อน" ดูไม่ถูกต้อง

คุณสามารถแปลไวยากรณ์ทั้งหมดของ Haskell เป็นภาษาคอร์ที่ง่ายมาก (ซึ่งจริง ๆ แล้วทำเมื่อรวบรวม) ดังนั้นไวยากรณ์ของ Haskell ส่วนใหญ่จึงถูกมองว่าเป็น "syntactic sugar" สำหรับบางสิ่งที่เป็นเพียงแลมบ์ดาแคลคูลัส อย่างไรก็ตามเราสามารถยอมรับได้ว่าโปรแกรม Haskell นั้นจัดการได้ง่ายกว่าและรัดกุมมากในขณะที่โปรแกรมที่แปลแล้วนั้นยากต่อการอ่านหรือคิด


2

หากการแจกจ่ายนั้นได้รับการแก้ไขในเวลารวบรวมขึ้นอยู่กับประเภทการแสดงออกของการโต้แย้งคุณคงสามารถยืนยันได้ว่ามันเป็น "น้ำตาลน้ำตาล" แทนสองวิธีที่ต่างกันด้วยชื่อที่ต่างกันโดยที่โปรแกรมเมอร์เขียน "รู้" ชนิดคงที่และ สามารถใช้ชื่อเมธอดที่ถูกต้องแทนชื่อที่โอเวอร์โหลด นอกจากนี้ยังเป็นรูปแบบของความหลากหลายคงที่ แต่ในรูปแบบที่ จำกัด นั้นมักจะไม่ได้มีประสิทธิภาพมาก

แน่นอนว่ามันจะเป็นการสร้างความรำคาญที่ต้องเปลี่ยนชื่อของเมธอดที่คุณเรียกเมื่อใดก็ตามที่คุณเปลี่ยนชนิดของตัวแปร แต่ตัวอย่างเช่นในภาษา C นั้นถือว่าเป็นความรำคาญที่สามารถจัดการได้ดังนั้น C จึงไม่มีฟังก์ชั่นการโหลดมากเกินไป ตอนนี้มันมีมาโครทั่วไป)

ในเทมเพลต C ++ และในภาษาใด ๆ ที่ทำการหักประเภทคงที่ที่ไม่สำคัญคุณไม่สามารถยืนยันได้ว่านี่คือ "น้ำตาลซินเทติก" เว้นแต่คุณจะยืนยันว่าการหักประเภทคงที่คือ "น้ำตาลเชิงเวท" มันจะเป็นการสร้างความรำคาญที่จะไม่มีเทมเพลตและในบริบทของ C ++ มันจะเป็น "ความรำคาญที่ไม่สามารถจัดการได้" เนื่องจากมันมีสำนวนต่อภาษาและไลบรารีมาตรฐานของมัน ดังนั้นใน C ++ มันเป็นมากกว่าผู้ช่วยที่ดีมันสำคัญกับสไตล์ของภาษาและดังนั้นฉันคิดว่าคุณต้องเรียกมันว่า "syntactic sugar" มากกว่า

ใน Java คุณอาจจะคิดว่ามันเป็นมากกว่าเพียงแค่ความสะดวกสบายเมื่อพิจารณาเช่นวิธีการหลาย overloads มีของและPrintStream.print PrintStream.printlnแต่มีDataInputStream.readXวิธีการมากมายตั้งแต่ Java ไม่ได้เกินประเภทผลตอบแทนดังนั้นในบางแง่มันเป็นเพียงเพื่อความสะดวก สิ่งเหล่านี้ล้วน แต่เป็นประเภทดั้งเดิม

ผมจำไม่ได้ว่าเกิดอะไรขึ้นในชวาถ้าฉันมีชั้นเรียนAและBการขยายOผมเกินวิธีการfoo(O), foo(A)และfoo(B)จากนั้นในทั่วไปกับ<T extends O>ผมเรียกfoo(t)ที่เป็นตัวอย่างของt Tในกรณีที่Tเป็นAฉันจะได้รับการจัดส่งขึ้นอยู่กับการโอเวอร์โหลดหรือจะเป็นถ้าฉันเรียกว่าfoo(O)?

หากก่อนหน้านี้เมธอด Java โอเวอร์โหลดจะดีกว่าน้ำตาลในแบบเดียวกับที่โอเวอร์โหลดของ C ++ ใช้คำจำกัดความของคุณฉันคิดว่าใน Java ฉันสามารถเขียนชุดตรวจสอบประเภท (ซึ่งจะเปราะบางเพราะ overloads ใหม่fooจะต้องตรวจสอบเพิ่มเติม) นอกเหนือจากการยอมรับความเปราะบางนั้นฉันไม่สามารถทำการเปลี่ยนแปลงในท้องถิ่นที่ไซต์การโทรเพื่อทำให้ถูกต้อง แต่ฉันต้องยอมแพ้ในการเขียนรหัสทั่วไป ฉันยืนยันว่าการป้องกันโค้ดที่ป่องอาจเป็นน้ำตาลเชิงประโยค แต่การป้องกันโค้ดที่เปราะบางนั้นยิ่งกว่านั้น ด้วยเหตุผลดังกล่าวการเกิด polymorphism แบบคงที่โดยทั่วไปจึงเป็นมากกว่าเพียงน้ำตาลซินแทคติค สถานการณ์ในภาษาใดภาษาหนึ่งอาจแตกต่างกันไปขึ้นอยู่กับว่าภาษานั้นให้คุณได้รับโดย "ไม่รู้" ชนิดคงที่


ใน Java โอเวอร์โหลดจะได้รับการแก้ไขในเวลารวบรวม ด้วยการใช้การลบประเภทมันจะเป็นไปไม่ได้สำหรับพวกเขาที่จะเป็นอย่างอื่น นอกจากนี้ได้โดยไม่ต้องลบออกประเภทถ้าT:Animalเป็นอยู่ในประเภทSiameseCatและเกินที่มีอยู่Cat Foo(Animal), SiameseCat Foo(Cat)และAnimal Foo(SiameseCat)ที่เกินควรจะเลือกถ้าTเป็นSiameseCat?
supercat

@supercat: เหมาะสม ดังนั้นฉันจึงสามารถหาคำตอบได้โดยไม่จำ (หรือแน่นอนเรียกใช้) ดังนั้นการโอเวอร์โหลดของ Java จึงไม่ดีไปกว่าน้ำตาลในลักษณะเดียวกับการโอเวอร์โหลด C ++ ที่เกี่ยวข้องกับรหัสทั่วไป มันยังคงเป็นไปได้มีวิธีอื่นที่ดีกว่าแค่การเปลี่ยนแปลงในท้องถิ่น ฉันสงสัยว่าฉันควรเปลี่ยนตัวอย่างของฉันเป็น C ++ หรือปล่อยให้มันเป็น Java-that- ไม่ใช่จริง Java
Steve Jessop

โอเวอร์โหลดสามารถเป็นประโยชน์ในกรณีที่เมธอดมีอาร์กิวเมนต์ที่เป็นทางเลือก แต่ก็อาจเป็นอันตรายได้เช่นกัน สมมติว่าสายได้รับการเปลี่ยนแปลงไปlong foo=Math.round(bar*1.0001)*5 long foo=Math.round(bar)*5สิ่งนั้นจะส่งผลต่อความหมายอย่างไรถ้าbarเท่ากับ 123456789L?
supercat

@supercat ฉันเถียงอันตรายจริงมีการแปลงนัยจากการlong double
Doval

@Doval: เพื่อdouble?
supercat

1

ดูเหมือนว่า "น้ำตาลซินแทติกติก" ดูจะเสื่อมเสียเหมือนไร้ประโยชน์หรือไม่สำคัญ นั่นคือเหตุผลที่คำถามทำให้เกิดคำตอบเชิงลบมากมาย

แต่คุณพูดถูกวิธีมากเกินไปไม่เพิ่มคุณสมบัติใด ๆ ให้กับภาษายกเว้นความเป็นไปได้ที่จะใช้ชื่อเดียวกันสำหรับวิธีการต่าง ๆ คุณสามารถทำให้ชนิดพารามิเตอร์ชัดเจนโปรแกรมจะยังคงทำงานเหมือนเดิม

เช่นเดียวกับชื่อแพ็คเกจ String เป็นเพียงน้ำตาลประโยคสำหรับ java.lang.String

ในความเป็นจริงวิธีการเช่น

void fun(int i, String c);

ในคลาส MyClass ควรเรียกสิ่งเช่น "my_package_MyClass_fun_int_java_lang_String" นี้จะระบุวิธีการที่ไม่ซ้ำกัน (JVM ทำอะไรแบบนั้นเป็นการภายใน) แต่คุณไม่ต้องการที่จะเขียน นั่นคือเหตุผลที่คอมไพเลอร์จะช่วยให้คุณเขียนสนุก (1, "หนึ่ง") และระบุวิธีการที่มันเป็น

อย่างไรก็ตามมีสิ่งหนึ่งที่คุณสามารถทำได้กับการโหลดมากเกินไป: หากคุณโอเวอร์โหลดเมธอดที่มีจำนวนอาร์กิวเมนต์เท่ากันคอมไพเลอร์จะคิดออกโดยอัตโนมัติว่าเวอร์ชันไหนเหมาะสมกับอาร์กิวเมนต์ที่ดีที่สุดที่กำหนดโดยอาร์กิวเมนต์ที่ตรงกันไม่ใช่เฉพาะกับประเภทที่เท่ากัน อาร์กิวเมนต์ที่กำหนดเป็น subclass ของอาร์กิวเมนต์ที่ประกาศ

หากคุณมีสองกระบวนการที่โอเวอร์โหลด

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

คุณไม่จำเป็นต้องรู้ว่ามีขั้นตอนเฉพาะสำหรับวันที่ addParameter ("สวัสดี", "โลก) จะโทรหารุ่นแรก, addParameter (" ตอนนี้ ", วันที่ใหม่ ()) จะเรียกอันที่สอง

แน่นอนคุณควรหลีกเลี่ยงวิธีการมากไปด้วยวิธีอื่นที่ทำสิ่งที่แตกต่างอย่างสิ้นเชิง


1

น่าสนใจคำตอบสำหรับคำถามนี้จะขึ้นอยู่กับภาษา

โดยเฉพาะมีปฏิสัมพันธ์ระหว่างการโอเวอร์โหลดและการเขียนโปรแกรมทั่วไป (*) และขึ้นอยู่กับวิธีการใช้งานการเขียนโปรแกรมทั่วไปมันอาจจะเป็นเพียงน้ำตาล syntactic (สนิม) หรือจำเป็นจริงๆ (C ++)

นั่นคือเมื่อการเขียนโปรแกรมทั่วไปถูกนำไปใช้กับอินเตอร์เฟสที่ชัดเจน (ใน Rust หรือ Haskell, เหล่านั้นจะเป็นคลาสประเภท), การโหลดมากเกินไปนั้นเป็นเพียงน้ำตาลเชิงวากยสัมพันธ์; หรือจริงๆแล้วอาจไม่ได้เป็นส่วนหนึ่งของภาษา

ในทางตรงกันข้ามเมื่อการเขียนโปรแกรมทั่วไปถูกนำมาใช้กับการพิมพ์เป็ด (ไม่ว่าจะเป็นแบบไดนามิกหรือแบบคงที่) จากนั้นชื่อของวิธีการเป็นสัญญาที่จำเป็นและการบรรทุกเกินพิกัดเป็นสิ่งจำเป็นสำหรับระบบในการทำงาน

(*) ใช้ในแง่ของการเขียนวิธีหนึ่งครั้งเพื่อใช้งานกับประเภทต่าง ๆ ในรูปแบบที่เหมือนกัน


0

ในบางภาษาก็ไม่ต้องสงสัยเลยว่าน้ำตาลเป็นประโยค อย่างไรก็ตามสิ่งที่เป็นน้ำตาลขึ้นอยู่กับมุมมองของคุณ ฉันจะออกจากการสนทนานี้ในภายหลังในคำตอบนี้

สำหรับตอนนี้ฉันแค่ต้องการที่จะทราบว่าในบางภาษามันไม่ได้เป็นน้ำตาล syntactic อย่างน้อยก็ไม่ได้โดยไม่ต้องให้คุณใช้ตรรกะ / อัลกอริทึมที่แตกต่างกันโดยสิ้นเชิงในการใช้สิ่งเดียวกัน มันก็เหมือนกับการอ้างสิทธิ์การเรียกซ้ำเป็นน้ำตาลเชิงประโยค (ซึ่งเป็นเพราะคุณสามารถเขียนอัลกอริทึมแบบเรียกซ้ำทั้งหมดด้วยลูปและสแต็ก)

ตัวอย่างหนึ่งของการใช้งานที่ยากมากที่จะมาแทนที่นั้นมาจากภาษาที่ไม่สามารถเรียกคุณลักษณะนี้ว่า "overloading function" ได้ แต่มันเรียกว่า "การจับคู่รูปแบบ" (ซึ่งสามารถดูได้ว่ามีการโหลดเกินพิกัดเนื่องจากเราสามารถโอเวอร์โหลดไม่ใช่ประเภท แต่เป็นค่า)

นี่คือการใช้ฟังก์ชัน Fibonacci ที่ไร้เดียงสาใน Haskell:

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

เนื้อหาทั้งสามฟังก์ชั่นสามารถถูกแทนที่ด้วย if / else เพราะมันเป็นเรื่องปกติในภาษาอื่น ๆ แต่พื้นฐานทำให้คำนิยามที่ง่ายที่สุด:

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

messier มากและไม่ได้โดยตรงแสดงความคิดทางคณิตศาสตร์ของลำดับฟีโบนักชี

ดังนั้นบางครั้งอาจเป็นไวยากรณ์ของน้ำตาลหากการใช้เพียงอย่างเดียวคืออนุญาตให้คุณเรียกใช้ฟังก์ชันที่มีอาร์กิวเมนต์ต่างกัน แต่บางครั้งมันก็เป็นพื้นฐานมากกว่านั้น


ตอนนี้สำหรับการแยกประเภทของสิ่งที่ผู้ประกอบการมากไปอาจเป็นน้ำตาลสำหรับ คุณระบุหนึ่งกรณีการใช้งาน - สามารถใช้เพื่อใช้ฟังก์ชันที่คล้ายกันซึ่งรับอาร์กิวเมนต์ที่แตกต่างกัน ดังนั้น:

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

สามารถนำไปใช้เป็น:

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

หรือแม้กระทั่ง:

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

แต่ตัวดำเนินการมากไปอาจเป็นน้ำตาลสำหรับการใช้อาร์กิวเมนต์ที่เป็นทางเลือก (บางภาษามีตัวดำเนินการมากไป แต่ไม่ใช่อาร์กิวเมนต์ที่เป็นทางเลือก):

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

อาจใช้ในการดำเนินการ:

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

ในภาษาดังกล่าว (google "ภาษา Ferite") การลบโอเปอเรเตอร์การบรรทุกเกินพิกัดอย่างมากจะลบคุณลักษณะหนึ่ง - อาร์กิวเมนต์ที่เป็นตัวเลือก ได้รับในภาษาที่มีคุณสมบัติทั้งสอง (c ++) การลบอย่างใดอย่างหนึ่งจะไม่มีผลกระทบสุทธิเนื่องจากทั้งสามารถใช้ในการดำเนินการขัดแย้งตัวเลือก


Haskell เป็นตัวอย่างที่ดีว่าทำไมตัวดำเนินการโอเวอร์โหลดไม่ใช่น้ำตาลเชิงประโยค แต่ฉันคิดว่าตัวอย่างที่ดีกว่าคือการแยกประเภทข้อมูลพีชคณิตด้วยการจับคู่รูปแบบ (สิ่งที่ไกลเกินกว่าที่ฉันรู้
11684

@ 11684: คุณสามารถชี้ไปที่ตัวอย่างได้หรือไม่? ฉันไม่รู้จักแฮสเค็ลล์อย่างแท้จริงเลย แต่ก็พบว่ารูปแบบของมันนั้นเข้ากันได้ดีกับความสง่างามเมื่อฉันเห็นตัวอย่างปด (บน computerphile บน youtube)
slebetman

รับประเภทข้อมูลเช่นdata PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfoคุณสามารถจับคู่รูปแบบกับตัวสร้างประเภท
11684

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เช่นนี้ ฉันหวังว่าความคิดเห็นนี้ค่อนข้างเข้าใจได้
11684

0

ฉันคิดว่ามันเป็นน้ำตาลในประโยคแบบง่าย ๆ ในภาษาส่วนใหญ่ (อย่างน้อยฉันก็รู้ ... ) เพราะพวกเขาทุกคนต้องใช้ Function-call ในเวลารวบรวม และคอมไพเลอร์ก็แทนที่การเรียกใช้ฟังก์ชันด้วยตัวชี้ที่ชัดเจนไปยังลายเซ็นการใช้งานที่ถูกต้อง

ตัวอย่างใน Java:

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

ดังนั้นในที่สุดมันก็ถูกแทนที่อย่างสมบูรณ์ด้วยคอมไพเลอร์แมโครอย่างง่ายด้วยการค้นหาและแทนที่แทนที่ฟังก์ชั่นโอเวอร์โหลด mangle ด้วย mangle_String และ mangle_int - เนื่องจากรายการอาร์กิวเมนต์เป็นส่วนหนึ่งของฟังก์ชั่นตัวระบุในที่สุดนี่คือสิ่งที่เกิดขึ้นจริง -> และ ดังนั้นจึงเป็นเพียงน้ำตาลวากยสัมพันธ์

ตอนนี้ถ้ามีภาษาที่ฟังก์ชันถูกกำหนดที่รันไทม์จริง ๆ เช่นเดียวกับเมธอด overridden ในวัตถุสิ่งนี้จะแตกต่างกัน แต่ฉันไม่คิดว่าจะมีภาษาเช่นนี้เนื่องจากวิธีการโอเวอร์โหลดมีแนวโน้มที่จะคลุมเครือซึ่งคอมไพเลอร์ไม่สามารถแก้ไขได้และจะต้องได้รับการจัดการโดยโปรแกรมเมอร์ที่มีบล็อกชัดเจน สิ่งนี้ไม่สามารถทำได้ที่รันไทม์


0

ในข้อมูลประเภท Java ถูกคอมไพล์และโอเวอร์โหลดใดที่ถูกเรียกจะตัดสินใจ ณ เวลารวบรวม

ต่อไปนี้เป็นตัวอย่างข้อมูลจาก sun.misc.Unsafe (ยูทิลิตี้สำหรับ Atomics) ตามที่ดูในเครื่องมือแก้ไขไฟล์คลาสของ 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]

ในขณะที่คุณสามารถดูข้อมูลประเภทของวิธีการที่ถูกเรียก (บรรทัดที่ 4) รวมอยู่ในการโทร

ซึ่งหมายความว่าคุณสามารถสร้างคอมไพเลอร์ java ที่รับข้อมูลประเภท ตัวอย่างเช่นการใช้สัญกรณ์ดังกล่าวแหล่งที่มาของข้างต้นจะเป็น:

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

และนักแสดงจะนานเป็นตัวเลือก

ในภาษาที่รวบรวมแบบสแตติกอื่น ๆ คุณจะเห็นการตั้งค่าที่คล้ายกันซึ่งคอมไพเลอร์จะตัดสินว่าโอเวอร์โหลดใดจะถูกเรียกขึ้นอยู่กับประเภทและรวมไว้ในการผูก / การโทร

ข้อยกเว้นคือไลบรารีไดนามิก C ซึ่งไม่รวมข้อมูลประเภทและการพยายามสร้างฟังก์ชั่นโอเวอร์โหลดจะทำให้ตัวเชื่อมโยงบ่น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.