दूसरों क्या कह रहे हैं, वापसी प्रकार से अधिक भार के विपरीत है संभव और है कुछ आधुनिक भाषाओं के द्वारा किया। सामान्य आपत्ति यह है कि जैसे कोड में है
int func();
string func();
int main() { func(); }
आप यह नहीं बता सकते कि किसे func()
बुलाया जा रहा है। इसे कुछ तरीकों से हल किया जा सकता है:
- इस तरह की स्थिति में किस फ़ंक्शन को कहा जाता है, यह निर्धारित करने के लिए एक पूर्वानुमान विधि है।
- जब भी ऐसी स्थिति होती है, यह एक संकलन-समय की त्रुटि है। हालाँकि, एक वाक्यविन्यास है जो प्रोग्रामर को अनुमति नहीं देता है, जैसे
int main() { (string)func(); }
।
- साइड इफेक्ट नहीं है। यदि आपके साइड इफेक्ट्स नहीं हैं और आप कभी किसी फ़ंक्शन के रिटर्न मान का उपयोग नहीं करते हैं, तो कंपाइलर पहले स्थान पर फ़ंक्शन को कॉल करने से कभी भी बच सकता है।
मैं नियमित रूप से ( ab ) भाषाओं में से दो वापसी प्रकार द्वारा अधिभार का उपयोग करता हूं : पर्ल और हास्केल । मुझे बताएं कि वे क्या करते हैं।
में पर्ल , वहाँ में एक बुनियादी अंतर है अदिश और सूची संदर्भ (और अन्य, लेकिन हम नाटक करता हूँ वहाँ दो हैं)। पर्ल में प्रत्येक अंतर्निहित फ़ंक्शन उस संदर्भ के आधार पर अलग-अलग चीजें कर सकता है जिसमें इसे कहा जाता है। उदाहरण के लिए, 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.
पर्ल में प्रत्येक ऑपरेटर स्केलर संदर्भ में कुछ और सूची के संदर्भ में कुछ करता है, और वे अलग-अलग हो सकते हैं, जैसा कि सचित्र है। (यह सिर्फ यादृच्छिक परिचालकों के लिए नहीं है localtime
। यदि आप @a
सूची के संदर्भ में किसी सरणी का उपयोग करते हैं , तो यह सरणी को लौटाता है, जबकि स्केलर संदर्भ में, यह तत्वों की संख्या लौटाता है। इसलिए उदाहरण के print @a
लिए तत्वों को print 0+@a
प्रिंट करता है , जबकि आकार को प्रिंट करता है। ) इसके अलावा, प्रत्येक ऑपरेटर एक संदर्भ को बाध्य कर सकता है, उदाहरण के लिए +
स्केलर संदर्भ को मजबूर करता है। man perlfunc
दस्तावेजों में हर प्रविष्टि । उदाहरण के लिए, यहां प्रविष्टि का हिस्सा है glob EXPR
:
सूची के संदर्भ में, EXPR
मानक यूनिक्स शेल के रूप में इस तरह के मूल्य पर फ़ाइल नाम विस्तार की एक (संभवतः खाली) सूची देता /bin/csh
है। स्केलर के संदर्भ में, ग्लोब इस तरह के फ़ाइलनाम विस्तार के माध्यम से पुनरावृत्त करता है, जब सूची समाप्त हो जाती है तो अपराजित हो जाती है।
अब, सूची और अदिश संदर्भ के बीच क्या संबंध है? खैर, man perlfunc
कहते हैं
निम्नलिखित महत्वपूर्ण नियम को याद रखें: ऐसा कोई नियम नहीं है जो सूची के संदर्भ में एक अभिव्यक्ति के व्यवहार को स्केलर के संदर्भ में, या उसके विपरीत व्यवहार से संबंधित करता है। यह दो पूरी तरह से अलग चीजें कर सकता है। प्रत्येक ऑपरेटर और फ़ंक्शन यह तय करता है कि स्केलर के संदर्भ में किस प्रकार का मान लौटना उचित होगा। कुछ ऑपरेटर सूची की लंबाई लौटाते हैं जो सूची के संदर्भ में वापस कर दी गई होती। कुछ ऑपरेटर सूची में पहला मान लौटाते हैं। कुछ ऑपरेटर सूची में अंतिम मान लौटाते हैं। कुछ ऑपरेटर सफल संचालन की एक गिनती लौटाते हैं। सामान्य तौर पर, वे वही करते हैं जो आप चाहते हैं, जब तक आप निरंतरता नहीं चाहते।
इसलिए यह एकल कार्य होने का एक साधारण मामला नहीं है, और फिर आप अंत में सरल रूपांतरण करते हैं। वास्तव में, मैंने localtime
उस कारण के लिए उदाहरण चुना ।
यह केवल बिल्ट-इन नहीं है जिसमें यह व्यवहार है। कोई भी उपयोगकर्ता ऐसे फ़ंक्शन का उपयोग करके परिभाषित कर सकता है wantarray
, जो आपको सूची, स्केलर और शून्य संदर्भ के बीच अंतर करने की अनुमति देता है। इसलिए, उदाहरण के लिए, यदि आप शून्य संदर्भ में कहे जा रहे हैं, तो आप कुछ नहीं करने का निर्णय ले सकते हैं।
अब, आप शिकायत कर सकते हैं कि यह रिटर्न वैल्यू द्वारा सही ओवरलोडिंग नहीं है क्योंकि आपके पास केवल एक फ़ंक्शन है, जिसे उस संदर्भ में बताया जाता है जिसे यह कहा जाता है और फिर उस जानकारी पर कार्य करता है। हालाँकि, यह स्पष्ट रूप से समतुल्य है (और कैसे पर्ल सामान्य रूप से ओवरलोडिंग की अनुमति नहीं देता है, लेकिन एक फ़ंक्शन इसके तर्कों की जांच कर सकता है)। इसके अलावा, यह अच्छी तरह से इस प्रतिक्रिया की शुरुआत में उल्लिखित अस्पष्ट स्थिति को हल करता है। पर्ल को यह शिकायत नहीं है कि यह पता नहीं है कि किस विधि को कॉल करना है; यह सिर्फ इसे कहता है। सभी को यह पता लगाना है कि फ़ंक्शन को किस संदर्भ में बुलाया गया था, जो हमेशा संभव है:
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"
(नोट: मैं कभी-कभी पर्ल ऑपरेटर से कह सकता हूं जब मेरा मतलब है कि यह इस चर्चा के लिए महत्वपूर्ण नहीं है।)
हास्केल दूसरे दृष्टिकोण को अपनाता है, जिसका दुष्प्रभाव नहीं है। इसमें एक मजबूत प्रकार की प्रणाली भी है, और इसलिए आप निम्नलिखित की तरह कोड लिख सकते हैं:
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
इसे पढ़ सकते हैं। हास्केल को कैसे पता चला कि मैं एक फ्लोटिंग पॉइंट नंबर पढ़ना चाहता था? खैर, का प्रकार sqrt
है sqrt :: Floating a => a -> a
, जो अनिवार्य रूप से मतलब है कि sqrt
केवल अस्थायी बिंदु संख्या को इनपुट के रूप में स्वीकार कर सकता है, और इसलिए हास्केल ने अनुमान लगाया कि मैं क्या चाहता था।
क्या होता है जब हास्केल जो चाहते हैं वह अनुमान नहीं लगा सकता है? खैर, कुछ संभावनाएं हैं। अगर मैं रिटर्न वैल्यू का उपयोग बिल्कुल नहीं करता हूं, तो हास्केल सीधे फ़ंक्शन को पहले स्थान पर नहीं बुलाएगा। हालांकि, अगर मैं कर दिया गया मान का उपयोग करें, तो हास्केल शिकायत है कि यह प्रकार अनुमान नहीं लगा सकता:
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 : "ऐसा प्रतीत हो सकता है कि सरलतम अधिभार संकल्प नियम सब कुछ का उपयोग करना है - सभी जानकारी यथासंभव विस्तृत संदर्भ से - अतिभारित संदर्भ को हल करने के लिए। यह नियम सरल हो सकता है, लेकिन यह उपयोगी नहीं है। इसके लिए मानव पाठक की आवश्यकता है। मनमाने ढंग से पाठ के बड़े टुकड़ों को स्कैन करने के लिए, और मनमाने ढंग से जटिल inferences (जैसे कि (g) ऊपर) को बनाने के लिए। हमारा मानना है कि एक बेहतर नियम वह है जो स्पष्ट रूप से कार्य करता है एक मानव पाठक या एक कंपाइलर को प्रदर्शन करना चाहिए, और इससे यह कार्य हो जाता है मानव पाठक के लिए जितना संभव हो उतना स्वाभाविक है। ”
C ++ (उपधारा 7.4.1of बज़्ने स्ट्रॉस्ट्रुप की "द 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()
अलगाव में कॉल को देखना और निर्धारित करना संभव नहीं होगा कि कौन सा फ़ंक्शन कहा जाता है। "(तुलना के लिए ध्यान दें, कि हास्केल में कोई अंतर्निहित रूपांतरण नहीं हैं ।)
जावा ( जावा लैंग्वेज स्पेसिफिकेशन 9.4.1 ): "विरासत में मिली विधियों में से एक को हर दूसरी विरासत में दी गई विधि के लिए रिटर्न-टाइप-सबटेबल होना चाहिए, या फिर कंपाइल-टाइम एरर आता है।" (हाँ, मुझे पता है कि यह एक तर्क नहीं देता है। मुझे यकीन है कि "जावा प्रोग्रामिंग लैंग्वेज" में गोसलिंग द्वारा तर्क दिया गया है। शायद किसी के पास एक प्रति है? मुझे यकीन है कि यह "कम से कम आश्चर्य का सिद्धांत" है। ) हालांकि, जावा के बारे में मजेदार तथ्य: जेवीएम रिटर्न वैल्यू द्वारा ओवरलोडिंग की अनुमति देता है ! इसका उपयोग, उदाहरण के लिए, स्काला में किया जाता है, और सीधे जावा के माध्यम से और साथ ही साथ इंटर्ल्स के साथ खेलकर इसे एक्सेस किया जा सकता है ।
पुनश्च। अंतिम नोट के रूप में, यह एक चाल के साथ सी ++ में रिटर्न वैल्यू द्वारा अधिभार करना संभव है। गवाह:
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
}