यह स्कैला में वाई-संयोजक का कार्यान्वयन है:
scala> def Y[T](func: (T => T) => (T => T)): (T => T) = func(Y(func))(_:T)
Y: [T](func: (T => T) => (T => T))T => T
scala> def fact = Y {
| f: (Int => Int) =>
| n: Int =>
| if(n <= 0) 1
| else n * f(n - 1)}
fact: Int => Int
scala> println(fact(5))
120
प्रश्न 1: परिणाम कैसा होता है 120
बाहर कदम, कदम से कदम? क्यों कि Y(func)
की तरह परिभाषित किया गया है func(Y(func))
, वाई अधिक से अधिक हो जाना चाहिए, वाई कहाँ खो गया है और कैसे है 120
peform प्रक्रिया में बाहर आओ?
प्रश्न 2: बीच क्या अंतर है
def Y[T](func: (T => T) => (T => T)): (T => T) = func(Y(func))(_:T)
तथा
def Y[T](func: (T => T) => (T => T)): (T => T) = func(Y(func))
वे स्केल आरईपीएल में एक ही प्रकार के हैं, लेकिन दूसरा परिणाम परिणाम मुद्रित नहीं कर सकता है 120
?
scala> def Y[T](func: (T => T) => (T => T)): (T => T) = func(Y(func))
Y: [T](func: (T => T) => (T => T))T => T
scala> def fact = Y {
| f: (Int => Int) =>
| n: Int =>
| if(n <= 0) 1
| else n * f(n - 1)}
fact: Int => Int
scala> println(fact(5))
java.lang.StackOverflowError
at .Y(<console>:11)
at .Y(<console>:11)
at .Y(<console>:11)
at .Y(<console>:11)
at .Y(<console>:11)
उत्तर:
उत्तर № 1 के लिए 7सबसे पहले, ध्यान दें कि यह एक वाई-Combinator, क्योंकि फ़ंक्शन का लैम्ब्डा संस्करण फ्री वैरिएबल वाई का उपयोग करता है। हालांकि वाई के लिए यह सही अभिव्यक्ति है, बस एक संयोजक नहीं।
तो, आइए पहले उस भाग को रखें जो फैक्टोरियल को एक अलग फ़ंक्शन में गणना करता है। हम इसे COMP कह सकते हैं:
def comp(f: Int => Int) =
(n: Int) => {
if (n <= 0) 1
else n * f(n - 1)
}
फैक्टोरियल फ़ंक्शन अब इस तरह बनाया जा सकता है:
def fact = Y(comp)
Q1:
वाई को func (वाई (func) के रूप में परिभाषित किया गया है)। हम तथ्य (5) का आह्वान करते हैं जो वास्तव में वाई (कॉम्प) (5) है, और वाई (कॉम्प) कॉम्प (वाई (कॉम्प)) का मूल्यांकन करता है। यह मुख्य बिंदु है: हम यहाँ रुको क्योंकि COMP एक समारोह लेता है और यह आवश्यकता होने तक इसका मूल्यांकन नहीं करता है। इसलिए, रनटाइम comp (???) के रूप में comp (y (comp)) को देखता है क्योंकि वाई (कॉम्प) भाग एक फ़ंक्शन है और केवल तभी मूल्यांकन किया जाएगा जब (यदि) आवश्यक हो।
क्या आप स्कैला में कॉल-बाय-वैल्यू और कॉल-बाय-नेम पैरामीटर के बारे में जानते हैं? यदि आप अपना पैरामीटर घोषित करते हैं someFunction(x: Int)
, जैसे ही कुछ फ़ंक्शन लागू किया जाता है, इसका मूल्यांकन किया जाएगा। लेकिन अगर आप इसे घोषित करते हैं someFunction(x: => Int)
, तो एक्स का तुरंत मूल्यांकन नहीं किया जाएगा, लेकिन परवह बिंदु जहां इसका उपयोग किया जाता है। दूसरा कॉल "नाम से कॉल करें" है और यह मूल रूप से आपके एक्स को "फ़ंक्शन जो कुछ भी नहीं लेता है और एक इंट देता है" के रूप में परिभाषित करता है। तो यदि आप 5 में गुजरते हैं, तो आप वास्तव में ऐसे फ़ंक्शन में गुजर रहे हैं जो 5 लौटाता है। इस तरह हम फ़ंक्शन पैरामीटर के आलसी मूल्यांकन प्राप्त करते हैं, क्योंकि उनके द्वारा उपयोग किए जाने वाले कार्यों पर फ़ंक्शंस का मूल्यांकन किया जाता है।
तो, comp में पैरामीटर एफ एक फ़ंक्शन है, इसलिए यहकेवल तभी मूल्यांकन किया जाता है जब आवश्यक हो, जो कि दूसरी शाखा में है। यही कारण है कि पूरी चीज काम करती है - वाई func की एक अनंत श्रृंखला बना सकता है (func (func (func (...))) लेकिन श्रृंखला आलसी है। प्रत्येक नए लिंक की गणना केवल तभी गणना की जाती है जब आवश्यक हो।
तो जब आप तथ्य (5) का आह्वान करते हैं, तो यह शरीर के माध्यम से दूसरी शाखा में चला जाएगा और केवल उस बिंदु पर मूल्यांकन किया जाएगा। इससे पहले नही। चूंकि आपका वाई comp () पैरामीटर f के रूप में पास हो गया है, इसलिए हम COMP () में फिर से गोता लगाएंगे। कॉम्प () के रिकर्सिव कॉल में हम 4 के फैक्टोरियल की गणना करेंगे। हम फिर कॉम्प फ़ंक्शन की दूसरी शाखा में जाएंगे, इस प्रकार प्रभावी ढंग से रिकर्सन के दूसरे स्तर (3 का फैक्टरियल गणना) में डाइविंग कर सकते हैं। ध्यान दें कि प्रत्येक फ़ंक्शन में आपके वाई ने कॉम्प को तर्क के रूप में एक COMP प्रदान किया है, लेकिन इसका मूल्यांकन केवल अन्य शाखा में किया जाता है। एक बार जब हम उस स्तर तक पहुंच जाते हैं जो 0 के फैक्टोरियल की गणना करता है, तो अगर शाखा ट्रिगर हो जाएगी और हम आगे डाइविंग बंद कर देंगे।
Q2:
इस
func(Y(func))(_:T)
इसके लिए वाक्यविन्यास चीनी है
x => func(Y(func))(x)
जिसका मतलब है कि हमने पूरी चीज को एक समारोह में लपेट लिया है। हमने ऐसा करके कुछ भी नहीं खोया, केवल प्राप्त किया।
हमने क्या हासिल किया? खैर, यह पिछले प्रश्न के उत्तर में एक ही चाल है; इस तरह हम इसे प्राप्त करते हैं func(Y(func))
केवल तभी मूल्यांकन किया जाएगा जब यह किसी फ़ंक्शन में लपेटा गया हो। इस तरह हम एक अनंत लूप से बचेंगे। एक फ़ंक्शन x => f (x) में एक (एकल-पैरामीटर) फ़ंक्शन f का विस्तार करना कहा जाता है ईटा-विस्तार (आप इसके बारे में और अधिक पढ़ सकते हैं यहाँ)।
ईटा-विस्तार का एक और सरल उदाहरण यहां दिया गया है: मान लें कि हमारे पास एक विधि है getSquare()
जो एक सरल देता है square()
फ़ंक्शन (यानी, एक फ़ंक्शन जो किसी संख्या के वर्ग की गणना करता है)। वर्ग (x) को सीधे लौटने के बजाय, हम एक फ़ंक्शन वापस कर सकते हैं जो x लेता है और वर्ग (x) देता है:
def square(x: Int) = x * x
val getSquare: Int => Int = square
val getSquare2: Int => Int = (x: Int) => square(x)
println(square(5)) // 25
println(getSquare(5)) // 25
println(getSquare2(5)) // 25
उम्मीद है की यह मदद करेगा।
जवाब के लिए 0 № 2
मुझे जवाब नहीं पता, लेकिन अनुमान लगाने की कोशिश करेगा। चूंकि आपके पास है def Y[T](f: ...) = f(...)
कंपाइलर विकल्प बदलने की कोशिश कर सकते हैं Y(f)
बस के साथ f
। यह एक अनंत अनुक्रम बना देगा f(f(f(...)))
। आंशिक रूप से आवेदन करना f
आप एक नई वस्तु बनाते हैं, और इस तरह के प्रतिस्थापन असंभव हो जाता है।