मैं सरल शब्दों में स्पष्टीकरण देने की कोशिश करूँगा। जैसा कि अन्य लोगों ने बताया है, हास्केल पर सिर का सामान्य रूप लागू नहीं होता है, इसलिए मैं इसे यहां नहीं मानूंगा।
सामान्य रूप
सामान्य रूप में एक अभिव्यक्ति का पूरी तरह से मूल्यांकन किया जाता है, और किसी भी उप-अभिव्यक्ति का किसी भी तरह से मूल्यांकन नहीं किया जा सकता है (यानी इसमें कोई मूल्यांकन नहीं किए गए खंड हैं)।
ये भाव सभी सामान्य रूप में हैं:
42
(2, "hello")
\x -> (x + 1)
ये भाव सामान्य रूप में नहीं हैं:
1 + 2 -- we could evaluate this to 3
(\x -> x + 1) 2 -- we could apply the function
"he" ++ "llo" -- we could apply the (++)
(1 + 1, 2 + 2) -- we could evaluate 1 + 1 and 2 + 2
कमजोर सिर सामान्य रूप
कमजोर सिर सामान्य रूप में एक अभिव्यक्ति का मूल्यांकन सबसे बाहरी डेटा कंस्ट्रक्टर या लैम्ब्डा एब्स्ट्रक्शन ( सिर ) के लिए किया गया है। उप-अभिव्यक्तियों का मूल्यांकन किया जा सकता है या नहीं । इसलिए, प्रत्येक सामान्य रूप की अभिव्यक्ति कमजोर सिर के सामान्य रूप में भी होती है, हालांकि इसके विपरीत सामान्य रूप से नहीं होता है।
यह निर्धारित करने के लिए कि कोई अभिव्यक्ति कमजोर सिर के सामान्य रूप में है, हमें केवल अभिव्यक्ति के सबसे बाहरी हिस्से को देखना होगा। यदि यह एक डेटा कंस्ट्रक्टर या लंबोदर है, तो यह कमजोर सिर सामान्य रूप में है। यदि यह एक फ़ंक्शन अनुप्रयोग है, तो यह नहीं है।
ये भाव कमजोर सिर वाले सामान्य रूप में हैं:
(1 + 1, 2 + 2) -- the outermost part is the data constructor (,)
\x -> 2 + 2 -- the outermost part is a lambda abstraction
'h' : ("e" ++ "llo") -- the outermost part is the data constructor (:)
जैसा कि उल्लेख किया गया है, ऊपर सूचीबद्ध सभी सामान्य रूप अभिव्यक्ति भी कमजोर सिर सामान्य रूप में हैं।
ये भाव कमजोर सिर वाले सामान्य रूप में नहीं हैं:
1 + 2 -- the outermost part here is an application of (+)
(\x -> x + 1) 2 -- the outermost part is an application of (\x -> x + 1)
"he" ++ "llo" -- the outermost part is an application of (++)
ढेर लगना
कमजोर सिर के सामान्य रूप की अभिव्यक्ति का मूल्यांकन करने के लिए आवश्यक है कि अन्य अभिव्यक्तियों का मूल्यांकन पहले WHNF को किया जाए। उदाहरण के लिए, 1 + (2 + 3)
WHNF का मूल्यांकन करने के लिए, हमें पहले मूल्यांकन करना होगा 2 + 3
। यदि किसी एकल अभिव्यक्ति का मूल्यांकन करने से इनमें से बहुत से नेस्टेड मूल्यांकन होते हैं, तो परिणाम एक ढेर अतिप्रवाह है।
यह तब होता है जब आप एक बड़ी अभिव्यक्ति का निर्माण करते हैं जो किसी भी डेटा निर्माता या लैम्ब्डा का उत्पादन नहीं करता है जब तक कि इसके बड़े हिस्से का मूल्यांकन नहीं किया गया हो। ये अक्सर इस तरह के उपयोग के कारण होते हैं foldl
:
foldl (+) 0 [1, 2, 3, 4, 5, 6]
= foldl (+) (0 + 1) [2, 3, 4, 5, 6]
= foldl (+) ((0 + 1) + 2) [3, 4, 5, 6]
= foldl (+) (((0 + 1) + 2) + 3) [4, 5, 6]
= foldl (+) ((((0 + 1) + 2) + 3) + 4) [5, 6]
= foldl (+) (((((0 + 1) + 2) + 3) + 4) + 5) [6]
= foldl (+) ((((((0 + 1) + 2) + 3) + 4) + 5) + 6) []
= (((((0 + 1) + 2) + 3) + 4) + 5) + 6
= ((((1 + 2) + 3) + 4) + 5) + 6
= (((3 + 3) + 4) + 5) + 6
= ((6 + 4) + 5) + 6
= (10 + 5) + 6
= 15 + 6
= 21
ध्यान दें कि अभिव्यक्ति को कमजोर सिर के सामान्य रूप में लाने से पहले इसे कितना गहरा जाना होगा।
आप आश्चर्यचकित हो सकते हैं कि हास्केल समय से पहले आंतरिक भावों को कम क्यों नहीं करता है? इसका कारण है हास्केल का आलस्य है। चूँकि यह सामान्य रूप से नहीं माना जा सकता है कि प्रत्येक उपप्रकार की जरूरत होगी, इसलिए भावों का मूल्यांकन बाहर से किया जाता है।
(जीएचसी के पास एक सख्त विश्लेषक है जो कुछ स्थितियों का पता लगाएगा जहां एक उपसंचाई की हमेशा आवश्यकता होती है और यह समय से पहले मूल्यांकन कर सकता है। यह केवल एक अनुकूलन है, हालांकि, और आपको इसे ओवरफ्लो से बचाने के लिए इस पर भरोसा नहीं करना चाहिए)।
दूसरी तरफ इस तरह की अभिव्यक्ति पूरी तरह से सुरक्षित है:
data List a = Cons a (List a) | Nil
foldr Cons Nil [1, 2, 3, 4, 5, 6]
= Cons 1 (foldr Cons Nil [2, 3, 4, 5, 6]) -- Cons is a constructor, stop.
इन बड़े भावों के निर्माण से बचने के लिए, जब हम जानते हैं कि सभी उपप्रकारों का मूल्यांकन करना होगा, हम समय से पहले मूल्यांकन करने के लिए आंतरिक भागों को बाध्य करना चाहते हैं।
seq
seq
एक विशेष कार्य है जिसका उपयोग अभिव्यक्तियों को बल देने के लिए किया जाता है। इसका शब्दार्थ यह seq x y
है कि जब भी y
कमजोर सिर के सामान्य रूप का मूल्यांकन किया जाता है, तो इसका मूल्यांकन कमजोर सिर के सामान्य रूप x
से भी किया जाता है।
यह अन्य स्थानों में से एक है foldl'
, जिसकी परिभाषा भिन्न रूप में है foldl
।
foldl' f a [] = a
foldl' f a (x:xs) = let a' = f a x in a' `seq` foldl' f a' xs
प्रत्येक पुनरावृत्ति foldl'
WHNF के लिए संचायक को बाध्य करता है। इसलिए यह एक बड़ी अभिव्यक्ति का निर्माण करने से बचता है, और इसलिए यह ढेर से बहने से बचता है।
foldl' (+) 0 [1, 2, 3, 4, 5, 6]
= foldl' (+) 1 [2, 3, 4, 5, 6]
= foldl' (+) 3 [3, 4, 5, 6]
= foldl' (+) 6 [4, 5, 6]
= foldl' (+) 10 [5, 6]
= foldl' (+) 15 [6]
= foldl' (+) 21 []
= 21 -- 21 is a data constructor, stop.
लेकिन HaskellWiki पर उदाहरण के रूप में, यह आपको सभी मामलों में नहीं बचाता है, क्योंकि संचायक का मूल्यांकन केवल WHNF के लिए किया जाता है। उदाहरण में, संचायक एक टपल है, इसलिए यह केवल टपल निर्माणकर्ता के मूल्यांकन को बाध्य करेगा, acc
या नहीं len
।
f (acc, len) x = (acc + x, len + 1)
foldl' f (0, 0) [1, 2, 3]
= foldl' f (0 + 1, 0 + 1) [2, 3]
= foldl' f ((0 + 1) + 2, (0 + 1) + 1) [3]
= foldl' f (((0 + 1) + 2) + 3, ((0 + 1) + 1) + 1) []
= (((0 + 1) + 2) + 3, ((0 + 1) + 1) + 1) -- tuple constructor, stop.
इससे बचने के लिए, हमें इसे बनाना चाहिए ताकि टपल निर्माणकर्ता बलों का मूल्यांकन acc
और len
। हम इसका उपयोग करके करते हैं seq
।
f' (acc, len) x = let acc' = acc + x
len' = len + 1
in acc' `seq` len' `seq` (acc', len')
foldl' f' (0, 0) [1, 2, 3]
= foldl' f' (1, 1) [2, 3]
= foldl' f' (3, 2) [3]
= foldl' f' (6, 3) []
= (6, 3) -- tuple constructor, stop.