मैंने स्काला फ़ंक्शंस ( स्काला के दूसरे दौरे का हिस्सा ) पढ़ा । उस पोस्ट में उन्होंने कहा:
तरीके और कार्य एक ही चीज नहीं हैं
लेकिन उन्होंने इसके बारे में कुछ भी नहीं बताया। वह क्या कहना चाह रहा था?
मैंने स्काला फ़ंक्शंस ( स्काला के दूसरे दौरे का हिस्सा ) पढ़ा । उस पोस्ट में उन्होंने कहा:
तरीके और कार्य एक ही चीज नहीं हैं
लेकिन उन्होंने इसके बारे में कुछ भी नहीं बताया। वह क्या कहना चाह रहा था?
जवाबों:
जिम ने अपने ब्लॉग पोस्ट में इसे बहुत कवर किया है , लेकिन मैं संदर्भ के लिए यहां एक ब्रीफिंग पोस्ट कर रहा हूं।
सबसे पहले, देखते हैं कि स्काला स्पेसिफिकेशन हमें क्या बताता है। अध्याय 3 (प्रकार) हमें फ़ंक्शन प्रकार (3.2.9) और विधि प्रकार (3.3.1) के बारे में बताते हैं । अध्याय 4 (मूल घोषणाएँ) मूल्य घोषणा और परिभाषाएँ (4.1), परिवर्तनीय घोषणा और परिभाषाएँ (4.2) और कार्य घोषणाएँ और परिभाषाएँ (4.6) बोलती हैं । अध्याय 6 (अभिव्यक्ति) बेनामी फ़ंक्शंस (6.23) और विधि मान (6.7) की बात करता है। उत्सुकता से, फ़ंक्शन मान 3.2.9 पर एक समय की बात की जाती है, और कोई और नहीं।
एक फ़ंक्शन प्रकार (लगभग) एक प्रकार का फॉर्म (T1, ..., Tn) => U है , जो FunctionN
मानक पुस्तकालय में विशेषता के लिए एक शॉर्टहैंड है । बेनामी फ़ंक्शंस और विधि मानों में फ़ंक्शन प्रकार होते हैं, और फ़ंक्शन प्रकारों का उपयोग मूल्य, चर और फ़ंक्शन घोषणाओं और परिभाषाओं के हिस्से के रूप में किया जा सकता है। वास्तव में, यह एक विधि प्रकार का हिस्सा हो सकता है।
एक विधि प्रकार एक गैर-मूल्य प्रकार है । इसका मतलब है कि कोई विधि - कोई वस्तु, कोई उदाहरण नहीं है - एक विधि प्रकार के साथ। जैसा कि ऊपर उल्लेख किया गया है, एक विधि मान वास्तव में एक फ़ंक्शन प्रकार है । एक विधि प्रकार एक def
घोषणा है - def
इसके शरीर को छोड़कर सब कुछ ।
मूल्य घोषणाएं और परिभाषाएँ और चर घोषणा और परिभाषाएं हैं val
और var
घोषणाओं, दोनों सहित प्रकार और मान - जो हो सकता है, क्रमशः, समारोह प्रकार और बेनामी कार्य या विधि मान । ध्यान दें कि, JVM पर, ये (विधि मान) जावा को "विधियों" के साथ लागू किया जाता है।
एक समारोह घोषणा एक है def
सहित घोषणा, प्रकार और शरीर । टाइप पार्ट मेथड टाइप है, और बॉडी एक एक्सप्रेशन या ब्लॉक है । यह JVM पर भी लागू होता है, जिसे Java "मेथड्स" कहता है।
अंत में, एक बेनामी फंक्शन एक फंक्शन टाइप का उदाहरण है (यानी, विशेषता का एक उदाहरण FunctionN
), और एक विधि मान एक ही बात है! भेद यह है कि विधियों से एक विधि मान बनाया जाता है, या तो एक अंडरस्कोर पोस्टफ़िक्स करके ( m _
"फ़ंक्शन डिक्लेरेशन" ( def
) m
), या एटा-विस्तार नामक एक प्रक्रिया द्वारा एक विधि मान है , जो विधि के लिए एक स्वचालित कलाकारों की तरह है कार्य करना।
यह चश्मा क्या कहता है, इसलिए मुझे इस बात को सामने रखना चाहिए: हम उस शब्दावली का उपयोग नहीं करते हैं! यह तथाकथित "फ़ंक्शन डिक्लेरेशन" के बीच बहुत अधिक भ्रम पैदा करता है , जो प्रोग्राम का एक हिस्सा है (अध्याय 4 - मूल घोषणाएं) और "अनाम फ़ंक्शन" , जो एक अभिव्यक्ति है, और "फ़ंक्शन प्रकार" , जो है, एक प्रकार - एक विशेषता।
नीचे दी गई शब्दावली, और अनुभवी स्काला प्रोग्रामर द्वारा उपयोग किया जाता है, विनिर्देश की शब्दावली से एक परिवर्तन करता है: फ़ंक्शन घोषणा कहने के बजाय , हम विधि कहते हैं । या विधि घोषणा भी। इसके अलावा, हम ध्यान दें कि मूल्य घोषणाएं और चर घोषणाएं भी व्यावहारिक उद्देश्यों के लिए विधियां हैं।
इसलिए, शब्दावली में उपरोक्त परिवर्तन को देखते हुए, यहाँ अंतर का व्यावहारिक विवरण दिया गया है।
एक समारोह एक वस्तु में से एक भी शामिल है FunctionX
जैसे लक्षण, Function0
, Function1
, Function2
, आदि यह भी शामिल है हो सकता है PartialFunction
के रूप में अच्छी तरह से जो वास्तव में फैली हुई है, Function1
।
आइए इन लक्षणों में से एक के लिए टाइप हस्ताक्षर देखें:
trait Function2[-T1, -T2, +R] extends AnyRef
इस विशेषता में एक सार विधि है (इसमें कुछ ठोस तरीके भी हैं):
def apply(v1: T1, v2: T2): R
और वह सब हमें बताएं कि इसके बारे में जानना है। एक फ़ंक्शन में एक apply
विधि होती है जो प्रकार T1 , T2 , ..., TN के एन मापदंडों को प्राप्त करती है , और कुछ प्रकार का रिटर्न देती है । यह प्राप्त मापदंडों पर कॉन्ट्रैक्ट-वेरिएंट है, और परिणाम पर सह-वेरिएंट है।R
उस विचरण का अर्थ है कि Function1[Seq[T], String]
एक उप-प्रकार है Function1[List[T], AnyRef]
। उपप्रकार होने का मतलब है कि इसका उपयोग इसके स्थान पर किया जा सकता है। कोई भी आसानी से देख सकता है कि अगर मैं कॉल करने जा रहा हूं f(List(1, 2, 3))
और AnyRef
बैक की उम्मीद कर रहा हूं , तो ऊपर दिए गए दो प्रकारों में से कोई भी काम करेगा।
अब, एक विधि और एक फ़ंक्शन की समानता क्या है ? ठीक है, अगर f
एक फ़ंक्शन है और m
गुंजाइश के लिए एक विधि स्थानीय है, तो दोनों को इस तरह कहा जा सकता है:
val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))
ये कॉल वास्तव में अलग हैं, क्योंकि पहला एक सिंटैक्टिक शुगर है। स्काला इसका विस्तार करता है:
val o1 = f.apply(List(1, 2, 3))
जो, ज़ाहिर है, ऑब्जेक्ट पर एक विधि कॉल है f
। फ़ंक्शंस में इसके लाभ के लिए अन्य सिंटैक्टिक शर्करा भी हैं: फ़ंक्शन शाब्दिक (उनमें से दो, वास्तव में) और (T1, T2) => R
हस्ताक्षर टाइप करें। उदाहरण के लिए:
val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
case i: Int => "Int"
case d: Double => "Double"
case o => "Other"
}
एक विधि और एक फ़ंक्शन के बीच एक और समानता यह है कि पूर्व को आसानी से बाद में परिवर्तित किया जा सकता है:
val f = m _
स्काला का विस्तार होगा , यह मानते हुए कि यह m
प्रकार है (List[Int])AnyRef
(स्केल 2.7):
val f = new AnyRef with Function1[List[Int], AnyRef] {
def apply(x$1: List[Int]) = this.m(x$1)
}
स्कैला 2.8 पर, यह वास्तव में AbstractFunction1
कक्षा के आकार को कम करने के लिए एक वर्ग का उपयोग करता है ।
ध्यान दें कि कोई एक फ़ंक्शन से किसी विधि तक - के आसपास दूसरे तरीके को परिवर्तित नहीं कर सकता है।
तरीकों, हालांकि, एक बड़ा फायदा है (अच्छी तरह से, दो - वे थोड़ा तेज हो सकते हैं): वे टाइप पैरामीटर प्राप्त कर सकते हैं । उदाहरण के लिए, जबकि f
ऊपर आवश्यक रूप से निर्दिष्ट कर सकते हैं कि List
यह किस प्रकार प्राप्त करता है ( List[Int]
उदाहरण में), m
इसे मानकीकृत कर सकता है:
def m[T](l: List[T]): String = l mkString ""
मुझे लगता है कि यह बहुत कुछ सब कुछ कवर करता है, लेकिन मैं किसी भी प्रश्न के उत्तर के साथ इसे पूरा करने में प्रसन्न रहूंगा।
val f = m
संकलक द्वारा विस्तार का उद्धरण करते हैं , जैसा कि val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
आपको इंगित करना चाहिए कि this
अंदर की apply
विधि AnyRef
ऑब्जेक्ट को संदर्भित नहीं करती है, लेकिन उस वस्तु को जिसका विधि में val f = m _
मूल्यांकन किया गया है ( बाहरी this
, इसलिए कहने के लिए ), चूंकि this
क्लोजर द्वारा कैप्चर किए गए मानों में से है (जैसे return
नीचे दिए गए बिंदु के अनुसार)।
एक विधि और एक फ़ंक्शन के बीच एक बड़ा व्यावहारिक अंतर यह है कि return
इसका क्या मतलब है। return
केवल कभी एक विधि से लौटता है। उदाहरण के लिए:
scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
val f = () => { return "test" }
^
एक विधि से परिभाषित फ़ंक्शन से वापस लौटना एक गैर-स्थानीय रिटर्न है:
scala> def f: String = {
| val g = () => { return "test" }
| g()
| "not this"
| }
f: String
scala> f
res4: String = test
जबकि एक स्थानीय विधि से लौटने पर केवल उस विधि से वापसी होती है।
scala> def f2: String = {
| def g(): String = { return "test" }
| g()
| "is this"
| }
f2: String
scala> f2
res5: String = is this
for (a <- List(1, 2, 3)) { return ... }
? कि एक बंद करने के लिए de-sugared हो जाता है।
return
समारोह से कोई मान है, और किसी न किसी रूप escape
या break
या continue
तरीकों से वापस जाने के लिए।
फ़ंक्शन किसी फ़ंक्शन को परिणाम उत्पन्न करने के लिए तर्कों की एक सूची के साथ आमंत्रित किया जा सकता है। एक फ़ंक्शन में एक पैरामीटर सूची, एक निकाय और एक परिणाम प्रकार होता है। ऐसे कार्य जो किसी वर्ग, गुण या एकल वर्ग के सदस्य हैं, विधियों को कहा जाता है । अन्य कार्यों के अंदर परिभाषित कार्यों को स्थानीय कार्य कहा जाता है। परिणाम प्रकार की इकाई के साथ कार्य प्रक्रियाओं को कहा जाता है। स्रोत कोड में बेनामी कार्यों को फ़ंक्शन शाब्दिक कहा जाता है। रन टाइम पर, फंक्शन शाब्दिक को फंक्शन वैल्यूज नामक ऑब्जेक्ट में इंस्टेंट किया जाता है।
स्कैला सेकेंड एडिशन में प्रोग्रामिंग। मार्टिन ओडस्की - लेक्स स्पून - बिल वेनर्स
मान लीजिए कि आपके पास एक सूची है
scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
एक विधि परिभाषित करें
scala> def m1(i:Int)=i+2
m1: (i: Int)Int
एक कार्य को परिभाषित करें
scala> (i:Int)=>i+2
res0: Int => Int = <function1>
scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
विधि तर्क स्वीकार करना
scala> m1(2)
res3: Int = 4
घाटी के साथ कार्य को परिभाषित करना
scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>
कार्य करने के लिए तर्क वैकल्पिक है
scala> p(2)
res4: Int = 4
scala> p
res5: Int => Int = <function1>
पद्धति का तर्क अनिवार्य है
scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function
निम्नलिखित ट्यूटोरियल की जाँच करें जो अन्य अंतरों को उदाहरणों के साथ समझाता है जैसे कि विधि बनाम कार्य के साथ भिन्नता के अन्य उदाहरण, फ़ंक्शन का उपयोग चर के रूप में, जो लौटे फ़ंक्शन का निर्माण करते हैं।
यहाँ एक अच्छा लेख है जहाँ से मेरे अधिकांश विवरण लिए गए हैं। मेरी समझ से संबंधित कार्यों और विधियों की बस थोड़ी सी तुलना। आशा करता हूँ की ये काम करेगा:
कार्य : वे मूल रूप से एक वस्तु हैं। अधिक सटीक रूप से, फ़ंक्शन एक लागू विधि के साथ ऑब्जेक्ट हैं; इसलिए, वे अपने ओवरहेड के कारण विधियों की तुलना में थोड़ा धीमा हैं। यह इस अर्थ में स्थिर विधियों के समान है कि वे किसी वस्तु के स्वतंत्र होने के लिए आमंत्रित हैं। किसी फ़ंक्शन का एक सरल उदाहरण केवल bellow की तरह है:
val f1 = (x: Int) => x + x
f1(2) // 4
ऊपर की पंक्ति ऑब्जेक्ट 1 = ऑब्जेक्ट 2 जैसी एक वस्तु को दूसरे को असाइन करने के अलावा कुछ भी नहीं है। वास्तव में हमारे उदाहरण में object2 एक अनाम फ़ंक्शन है और बाईं ओर ऑब्जेक्ट उसी के कारण मिलता है। इसलिए, अब f1 एक ऑब्जेक्ट (फ़ंक्शन) है। अनाम फ़ंक्शन वास्तव में Function1 [Int, Int] का एक उदाहरण है, जिसका अर्थ है एक प्रकार का Int और वापसी मान टाइप 1 का पैरामीटर। तर्कों के बिना f1 को कॉल करने से हमें अनाम फ़ंक्शन (Int => Int =) का हस्ताक्षर मिलेगा
विधियाँ : वे एक वस्तु नहीं हैं, बल्कि एक वर्ग के उदाहरण के लिए दी गई हैं, अर्थात, एक वस्तु। वास्तव में j ++ या c ++ में सदस्य कार्यों के रूप में एक ही विधि (जैसा कि रफ़ी खाचदौरीयन ने इस प्रश्न के लिए एक टिप्पणी में बताया है ) और आदि। एक विधि का एक सरल उदाहरण केवल bellow की तरह है:
def m1(x: Int) = x + x
m1(2) // 4
ऊपर दी गई लाइन एक साधारण मान असाइनमेंट नहीं है, बल्कि एक विधि की परिभाषा है। जब आप दूसरी पंक्ति की तरह मान 2 के साथ इस विधि को लागू करते हैं, तो x को 2 के साथ प्रतिस्थापित किया जाता है और परिणाम की गणना की जाएगी और आपको आउटपुट के रूप में 4 मिलता है। यहां आपको एक त्रुटि मिलेगी अगर बस एम 1 लिखो क्योंकि यह विधि है और इनपुट मूल्य की आवश्यकता है। _ का उपयोग करके आप किसी फ़ंक्शन को bellow जैसी विधि असाइन कर सकते हैं:
val f2 = m1 _ // Int => Int = <function1>
यहां रोब नोरिस द्वारा एक महान पोस्ट है जो अंतर को समझाता है, यहां एक टीएल; डीआर है
स्काला के तरीके मूल्य नहीं हैं, लेकिन कार्य हैं। आप एक ऐसे कार्य का निर्माण कर सकते हैं जो method-विस्तार (अनुगामी अंडरस्कोर थैली द्वारा ट्रिगर) के माध्यम से एक विधि को दर्शाता है।
निम्नलिखित परिभाषा के साथ:
एक विधि कुछ के साथ परिभाषित किया गया है डीईएफ़ और एक मूल्य कुछ आप एक को निर्दिष्ट कर सकते वैल
संक्षेप में ( ब्लॉग से निकालें ):
जब हम एक विधि को परिभाषित करते हैं तो हम देखते हैं कि हम इसे असाइन नहीं कर सकते हैं val
।
scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int
scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
val f = add1
नोट भी प्रकार है add1
, जो सामान्य नहीं लगता है; आप प्रकार का एक चर घोषित नहीं कर सकते (n: Int)Int
। तरीके मूल्य नहीं हैं।
हालांकि, η-विस्तार पोस्टफिक्स ऑपरेटर (pron उच्चारण "एटा") को जोड़कर, हम विधि को फ़ंक्शन मान में बदल सकते हैं। के प्रकार पर ध्यान दें f
।
scala> val f = add1 _
f: Int => Int = <function1>
scala> f(3)
res0: Int = 4
इसका प्रभाव _
निम्नलिखित के बराबर प्रदर्शन करना है: हम एक Function1
उदाहरण का निर्माण करते हैं जो हमारी पद्धति को दर्शाता है।
scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>
scala> g(3)
res18: Int = 4
स्कैला 2.13 में, कार्यों के विपरीत, विधियाँ ले / लौट सकती हैं
हालाँकि, इन प्रतिबंधों को Polymorphic function type # 4672 द्वारा dotty (Scala 3) में उठाया गया है , उदाहरण के लिए, dotty संस्करण 0.23.0-RC1 निम्नलिखित सिंटैक्स को सक्षम करता है
प्रकार के पैरामीटर
def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))
निहित पैरामीटर ( संदर्भ पैरामीटर)
def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero
आश्रित प्रकार
class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet
अधिक उदाहरणों के लिए, परीक्षण / रन / पॉलीमॉर्फिक-फ़ंक्शन.काला देखें
व्यावहारिक रूप से, एक स्काला प्रोग्रामर को केवल फ़ंक्शन और विधियों का ठीक से उपयोग करने के लिए निम्नलिखित तीन नियमों को जानना होगा:
def
कार्य द्वारा परिभाषित और कार्य शाब्दिक द्वारा परिभाषित तरीके =>
कार्य हैं। इसे पृष्ठ 143, अध्याय 8 में स्कैला में प्रोग्रामिंग की पुस्तक में 4 वें संस्करण में परिभाषित किया गया है।someNumber.foreach(println)
स्काला में प्रोग्रामिंग के चार संस्करणों के बाद, यह अभी भी लोगों के लिए दो महत्वपूर्ण अवधारणाओं को अलग करने के लिए एक मुद्दा है: फ़ंक्शन और फ़ंक्शन मान क्योंकि सभी संस्करण स्पष्ट विवरण नहीं देते हैं। भाषा विनिर्देश बहुत जटिल है। मैंने पाया कि उपरोक्त नियम सरल और सटीक हैं।