ขัดกับสิ่งที่คนอื่นจะพูดมากไปตามประเภทของผลตอบแทนที่เป็นไปได้และจะทำโดยภาษาสมัยใหม่บาง คัดค้านปกติคือในรหัสเช่น
int func();
string func();
int main() { func(); }
คุณไม่สามารถบอกfunc()
ได้ว่ากำลังเรียกใคร วิธีนี้สามารถแก้ไขได้ในสองสามวิธี:
- มีวิธีการที่คาดเดาได้เพื่อกำหนดฟังก์ชั่นที่เรียกว่าในสถานการณ์ดังกล่าว
- เมื่อใดก็ตามที่สถานการณ์ดังกล่าวเกิดขึ้นจะเป็นข้อผิดพลาดในการรวบรวมเวลา
int main() { (string)func(); }
อย่างไรก็ตามมีไวยากรณ์ที่ช่วยให้โปรแกรมเมอร์ที่จะกระจ่างเช่น
- ไม่มีผลข้างเคียง หากคุณไม่มีผลข้างเคียงและไม่เคยใช้ค่าส่งคืนของฟังก์ชันคอมไพเลอร์สามารถหลีกเลี่ยงการเรียกใช้ฟังก์ชันได้ตั้งแต่แรก
สองภาษาที่ฉันเป็นประจำ ( AB ) การใช้งานเกินพิกัดตามประเภทผลตอบแทน: PerlและHaskell ให้ฉันอธิบายสิ่งที่พวกเขาทำ
ในPerlมีความแตกต่างพื้นฐานระหว่างสเกลาร์และบริบทรายการ (และอื่น ๆ แต่เราจะทำเป็นว่ามีสองอย่าง) ทุกฟังก์ชั่นในตัวใน Perl สามารถทำสิ่งที่แตกต่างกันขึ้นอยู่กับบริบทที่เรียกว่า ตัวอย่างเช่นjoin
โอเปอเรเตอร์บังคับให้แสดงรายการบริบท (ในสิ่งที่เข้าร่วม) ในขณะที่scalar
โอเปอเรเตอร์บังคับให้ใช้บริบทสเกลาร์ดังนั้นให้เปรียบเทียบ:
print join " ", localtime(); # printed "58 11 2 14 0 109 3 13 0" for me right now
print scalar localtime(); # printed "Wed Jan 14 02:12:44 2009" for me right now.
ผู้ประกอบการทุกคนใน Perl ทำอะไรบางอย่างในบริบทเซนต์คิตส์และในบริบทรายการและพวกเขาอาจแตกต่างกันตามที่แสดง (นี่ไม่ได้มีไว้สำหรับตัวดำเนินการสุ่มlocaltime
เท่านั้นเช่นถ้าคุณใช้อาร์เรย์@a
ในบริบทรายการมันจะส่งกลับอาร์เรย์ในขณะที่อยู่ในบริบทสเกลามันจะส่งกลับจำนวนองค์ประกอบดังนั้นตัวอย่างเช่นprint @a
พิมพ์องค์ประกอบออกมาในขณะที่print 0+@a
พิมพ์ขนาด ) นอกจากนี้ผู้ประกอบการทุกคนสามารถบังคับบริบทเช่นการเพิ่ม+
กองกำลังบริบทสเกลาร์ ทุกรายการในman perlfunc
เอกสารนี้ ตัวอย่างเช่นนี่เป็นส่วนหนึ่งของรายการสำหรับglob EXPR
:
ในบริบทรายการส่งกลับรายการ (อาจจะเป็นที่ว่างเปล่า) การขยายชื่อไฟล์กับค่าของEXPR
เช่น Unix มาตรฐานเปลือก/bin/csh
จะทำ ในบริบทสเกลาร์, glob ซ้ำผ่านการขยายชื่อไฟล์ดังกล่าวกลับ undef เมื่อรายการหมด
ทีนี้ความสัมพันธ์ระหว่างรายการกับบริบทสเกลาร์คืออะไร เอาละman perlfunc
พูดว่า
จำกฎสำคัญต่อไปนี้: ไม่มีกฎที่เกี่ยวข้องกับพฤติกรรมของนิพจน์ในบริบทของรายการกับพฤติกรรมในบริบทสเกลาหรือในทางกลับกัน มันอาจทำสองสิ่งที่แตกต่างกันโดยสิ้นเชิง ผู้ประกอบการและฟังก์ชั่นแต่ละคนตัดสินใจว่าประเภทของมูลค่ามันจะเหมาะสมที่สุดที่จะกลับมาในบริบทสเกลา ตัวดำเนินการบางตัวส่งคืนความยาวของรายการที่จะถูกส่งคืนในบริบทรายการ ผู้ประกอบการบางคนกลับค่าแรกในรายการ ผู้ประกอบการบางคนกลับค่าสุดท้ายในรายการ ผู้ประกอบการบางคนกลับมานับการดำเนินงานที่ประสบความสำเร็จ โดยทั่วไปแล้วพวกเขาทำสิ่งที่คุณต้องการเว้นแต่คุณจะต้องการความมั่นคง
ดังนั้นจึงไม่ใช่เรื่องง่ายที่จะมีฟังก์ชั่นเดียวจากนั้นคุณก็ทำการแปลงอย่างง่ายในตอนท้าย อันที่จริงฉันเลือกlocaltime
ตัวอย่างด้วยเหตุผลนั้น
ไม่ใช่เพียงแค่บิวด์อินที่มีพฤติกรรมนี้ ผู้ใช้สามารถกำหนดฟังก์ชั่นดังกล่าวโดยใช้wantarray
ซึ่งช่วยให้คุณสามารถแยกความแตกต่างระหว่างรายการสเกลาร์และบริบทโมฆะ ตัวอย่างเช่นคุณสามารถตัดสินใจที่จะไม่ทำอะไรเลยถ้าคุณถูกเรียกในบริบทที่เป็นโมฆะ
ทีนี้คุณอาจจะบ่นว่านี่มันไม่ได้เป็นภาระเกินจริงโดยการคืนค่าเพราะคุณมีฟังก์ชั่นเดียวเท่านั้นซึ่งจะบอกถึงบริบทที่มันถูกเรียกใช้และจากนั้นทำหน้าที่กับข้อมูลนั้น อย่างไรก็ตามสิ่งนี้เทียบเท่ากันอย่างชัดเจน (และคล้ายกับวิธีที่ Perl ไม่อนุญาตให้มีการโหลดมากเกินไปตามปกติ แต่ฟังก์ชั่นสามารถตรวจสอบข้อโต้แย้งของมันได้) นอกจากนี้ยังช่วยแก้ไขสถานการณ์ที่คลุมเครือซึ่งได้กล่าวไว้ในตอนต้นของการตอบสนองนี้ Perl ไม่ได้บ่นว่าไม่รู้ว่าจะใช้วิธีไหน มันแค่เรียกมันว่า สิ่งที่ต้องทำคือหาว่าบริบทใดที่ฟังก์ชันถูกเรียกใช้ซึ่งเป็นไปได้เสมอ:
sub func {
if( not defined wantarray ) {
print "void\n";
} elsif( wantarray ) {
print "list\n";
} else {
print "scalar\n";
}
}
func(); # prints "void"
() = func(); # prints "list"
0+func(); # prints "scalar"
(หมายเหตุ: บางครั้งฉันอาจพูดว่าผู้ประกอบการ Perl เมื่อฉันหมายถึงฟังก์ชั่นนี้ไม่สำคัญกับการสนทนานี้)
Haskellใช้วิธีการอื่นคือไม่มีผลข้างเคียง นอกจากนี้ยังมีระบบการพิมพ์ที่แข็งแกร่งดังนั้นคุณจึงสามารถเขียนโค้ดดังนี้:
main = do n <- readLn
print (sqrt n) -- note that this is aligned below the n, if you care to run this
รหัสนี้อ่านตัวเลขทศนิยมจากอินพุตมาตรฐานและพิมพ์สแควร์รูท แต่สิ่งที่น่าแปลกใจเกี่ยวกับเรื่องนี้คืออะไร? ดีประเภทของการเป็นreadLn
readLn :: Read a => IO a
สิ่งนี้หมายความว่าสำหรับประเภทใด ๆ ที่สามารถRead
(อย่างเป็นทางการทุกประเภทที่เป็นตัวอย่างของRead
คลาสประเภท) readLn
สามารถอ่านได้ Haskell รู้ได้อย่างไรว่าฉันต้องการอ่านเลขทศนิยม ชนิดของsqrt
คือsqrt :: Floating a => a -> a
ซึ่งโดยพื้นฐานแล้วหมายความว่าsqrt
สามารถยอมรับเฉพาะตัวเลขจุดลอยตัวเป็นอินพุตเท่านั้นดังนั้น Haskell จึงสรุปสิ่งที่ฉันต้องการ
จะเกิดอะไรขึ้นเมื่อ Haskell ไม่สามารถสรุปสิ่งที่ฉันต้องการได้ มีความเป็นไปได้น้อย ถ้าฉันไม่ใช้ค่าส่งคืน Haskell จะไม่เรียกฟังก์ชันในตอนแรก แต่ถ้าผมทำใช้ค่าตอบแทนแล้ว Haskell จะบ่นว่ามันไม่สามารถอนุมานชนิด:
main = do n <- readLn
print n
-- this program results in a compile-time error "Unresolved top-level overloading"
ฉันสามารถแก้ไขความกำกวมโดยการระบุประเภทที่ฉันต้องการ:
main = do n <- readLn
print (n::Int)
-- this compiles (and does what I want)
อย่างไรก็ตามสิ่งที่การสนทนาทั้งหมดนี้หมายถึงว่าเป็นไปได้มากเกินไปโดยค่าตอบแทนเป็นไปได้และจะทำซึ่งตอบคำถามของคุณส่วนหนึ่ง
อีกส่วนหนึ่งของคำถามของคุณคือทำไมภาษาอื่น ๆ ถึงไม่ทำ ฉันจะให้คนอื่นตอบว่า อย่างไรก็ตามความคิดเห็นบางประการ: เหตุผลหลักอาจเป็นไปได้ว่าโอกาสสำหรับความสับสนนั้นยิ่งใหญ่กว่าการโต้แย้งตามประเภทอาร์กิวเมนต์ คุณสามารถดูเหตุผลจากภาษาต่าง ๆ :
Ada : "อาจดูเหมือนว่ากฎการแก้ปัญหาการโอเวอร์โหลดที่ง่ายที่สุดคือการใช้ทุกอย่าง - ข้อมูลทั้งหมดจากบริบทที่กว้างที่สุดเท่าที่จะทำได้ - เพื่อแก้ไขการอ้างอิงที่มากเกินไปกฎนี้อาจเรียบง่าย แต่ไม่เป็นประโยชน์ เพื่อสแกนข้อความขนาดใหญ่โดยพลการและทำการอนุมานที่ซับซ้อนตามอำเภอใจ (เช่น (g) ด้านบน) เราเชื่อว่ากฎที่ดีกว่าคือกฎที่ดีกว่าที่จะทำให้งานที่มนุษย์อ่านหรือคอมไพเลอร์ต้องทำอย่างชัดเจนและทำให้งานนี้ เป็นธรรมชาติสำหรับผู้อ่านของมนุษย์มากที่สุด "
C ++ (ส่วนย่อย 7.4.1 จาก Bjarne Stroustrup ของ "ภาษาการเขียนโปรแกรม C ++"): "ประเภทการส่งคืนจะไม่ได้รับการพิจารณาในการแก้ปัญหาการโอเวอร์โหลดเหตุผลคือเพื่อให้การแก้ไขสำหรับผู้ประกอบการแต่ละรายหรือการเรียกใช้ฟังก์ชัน
float sqrt(float);
double sqrt(double);
void f(double da, float fla)
{
float fl = sqrt(da); // call sqrt(double)
double d = sqrt(da); // call sqrt(double)
fl = sqrt(fla); // call sqrt(float)
d = sqrt(fla); // call sqrt(float)
}
หากคำนึงถึงประเภทการคืนสินค้าจะไม่สามารถดูการโทรsqrt()
แยกและกำหนดฟังก์ชันที่เรียกว่า "(หมายเหตุสำหรับการเปรียบเทียบใน Haskell จะไม่มีการแปลงโดยนัย )
Java ( Java Language Specification 9.4.1 ): "หนึ่งในวิธีการที่สืบทอดมานั้นจะต้องเป็น return-type-substitutable ทุก ๆ วิธีที่สืบทอดมาอื่น ๆ หรือมิฉะนั้นจะเกิดข้อผิดพลาดในการรวบรวมเวลา" (ใช่ฉันรู้ว่านี่ไม่ได้ให้เหตุผลฉันแน่ใจว่าเหตุผลที่ได้รับจาก Gosling ใน "ภาษาการเขียนโปรแกรมภาษาจาวา" บางทีอาจมีบางคนมีสำเนาฉันคิดว่ามันเป็น "หลักการของความประหลาดใจอย่างน้อย" ) อย่างไรก็ตามข้อเท็จจริงที่สนุกสนานเกี่ยวกับ Java: JVM อนุญาตให้โหลดเกินโดยส่งคืนค่า! ตัวอย่างนี้ใช้ในScalaและสามารถเข้าถึงได้โดยตรงผ่าน Javaเช่นกันโดยการเล่นกับผู้เล่นภายใน
PS ในฐานะโน้ตสุดท้ายเป็นไปได้จริงที่จะโอเวอร์โหลดโดยส่งคืนค่าใน C ++ พร้อมกับเล่ห์เหลี่ยม พยาน:
struct func {
operator string() { return "1";}
operator int() { return 2; }
};
int main( ) {
int x = func(); // calls int version
string y = func(); // calls string version
double d = func(); // calls int version
cout << func() << endl; // calls int version
func(); // calls neither
}