कमजोर हेड नॉर्मल फॉर्म क्या है?


290

क्या करता है कमजोर हेड सामान्य प्रपत्र (WHNF) मतलब? क्या करता है प्रमुख नॉर्मल फॉर्म (HNF) और सामान्य प्रपत्र (NF) मतलब?

वास्तविक विश्व हास्केल कहता है:

परिचित seq फ़ंक्शन एक अभिव्यक्ति का मूल्यांकन करता है जिसे हम हेड सामान्य रूप (संक्षिप्त HNF) कहते हैं। एक बार जब यह सबसे बाहरी कंस्ट्रक्टर ("हेड") पर पहुंच जाता है। यह सामान्य रूप (एनएफ) से अलग है, जिसमें एक अभिव्यक्ति का पूरी तरह से मूल्यांकन किया जाता है।

आपने हास्केल प्रोग्रामर्स को कमजोर सामान्य रूप (WHNF) के बारे में सुना होगा। सामान्य आंकड़ों के लिए, कमजोर सिर सामान्य रूप सिर के सामान्य रूप के समान है। अंतर केवल कार्यों के लिए उत्पन्न होता है, और यहां चिंता करने के लिए बहुत ही अपमानजनक है।

मैंने कुछ संसाधन और परिभाषाएँ ( हास्केल विकी और हास्केल मेल लिस्ट और फ्री डिक्शनरी ) पढ़ी हैं, लेकिन मुझे नहीं मिली। क्या कोई शायद एक उदाहरण दे सकता है या एक आम आदमी की परिभाषा प्रदान कर सकता है?

मैं अनुमान लगा रहा हूं कि यह इसके समान होगा:

WHNF = thunk : thunk

HNF = 0 : thunk 

NF = 0 : 1 : 2 : 3 : []

कैसे करते seqऔर ($!)WHNF और HNF से संबंधित हैं?

अपडेट करें

मैं अभी भी उलझन में हूं। मुझे पता है कि कुछ उत्तर एचएनएफ को नजरअंदाज करने के लिए कहते हैं। विभिन्न परिभाषाओं को पढ़ने से ऐसा लगता है कि WHNF और HNF में नियमित डेटा के बीच कोई अंतर नहीं है। हालांकि, ऐसा लगता है कि जब यह एक समारोह में आता है तो अंतर होता है। अगर कोई अंतर नहीं था, तो क्यों seqआवश्यक है foldl'?

भ्रम का एक अन्य बिंदु हास्केल विकी से है, जो बताता है कि seqWHNF को कम करता है, और निम्न उदाहरण के लिए कुछ भी नहीं करेगा। तब वे कहते हैं कि उन्हें seqमूल्यांकन को मजबूर करने के लिए उपयोग करना होगा। क्या यह एचएनएफ के लिए मजबूर नहीं है?

आम नौसिखिया ढेर अतिप्रवाह कोड:

myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)

जो लोग seq और कमजोर सिर को सामान्य रूप (whnf) समझते हैं वे तुरंत समझ सकते हैं कि यहां क्या गलत है। (acc + x, len + 1) पहले से ही whnf में है, इसलिए seq, जो whnf के मान को कम करता है, इससे कुछ नहीं होता है। यह कोड मूल तह उदाहरण की तरह थ्रक्स का निर्माण करेगा, वे बस एक टपल के अंदर होंगे। समाधान केवल टपल के घटकों को बाध्य करने के लिए है, जैसे

myAverage = uncurry (/) . foldl' 
          (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)

- स्टैकओवरफ़्लो पर हास्केल विकी


1
आम तौर पर हम WHNF और RNF की बात करते हैं। (RNF जा रहा है जिसे आप NF कहते हैं)
वैकल्पिक

5
@monadic RNF में R क्या दर्शाता है?
dave4420

7
@ dave4420: कम
मार्क

जवाबों:


399

मैं सरल शब्दों में स्पष्टीकरण देने की कोशिश करूँगा। जैसा कि अन्य लोगों ने बताया है, हास्केल पर सिर का सामान्य रूप लागू नहीं होता है, इसलिए मैं इसे यहां नहीं मानूंगा।

सामान्य रूप

सामान्य रूप में एक अभिव्यक्ति का पूरी तरह से मूल्यांकन किया जाता है, और किसी भी उप-अभिव्यक्ति का किसी भी तरह से मूल्यांकन नहीं किया जा सकता है (यानी इसमें कोई मूल्यांकन नहीं किए गए खंड हैं)।

ये भाव सभी सामान्य रूप में हैं:

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.

31
सिर के सामान्य रूप की आवश्यकता होती है कि लैम्बडा का शरीर कम हो जाता है, जबकि कमजोर सिर के सामान्य रूप की यह आवश्यकता नहीं होती है। तो \x -> 1 + 1WHNF है लेकिन HNF नहीं है।
हमर

विकिपीडिया बताता है कि HNF "[a] शब्द सामान्य स्थिति में है यदि सिर की स्थिति में बीटा-रेडेक्स नहीं है"। क्या हास्केल "कमजोर" है क्योंकि यह बीटा-रेडेक्स उप-अभिव्यक्तियों को नहीं करता है?

कैसे सख्त डेटा निर्माता नाटक में आते हैं? क्या वे सिर्फ seqअपने तर्कों पर कॉल करना पसंद कर रहे हैं?
बरगी

1
@ कैप्टेनओबर्ज़: 1 + 2 न तो NF है और न ही WHNF। भाव हमेशा एक सामान्य रूप में नहीं होते हैं।
हमार

2
@Zorobay: परिणाम को प्रिंट करने के लिए, GHCi अभिव्यक्ति का मूल्यांकन पूरी तरह से NF के लिए करता है, न कि केवल WHNF के लिए। दो वेरिएंट के बीच अंतर बताने का एक तरीका है मेमोरी स्टैट्स को सक्षम करना :set +s। फिर आप देख सकते हैं कि foldl' fअधिक थ्रो आवंटित करने से समाप्त होता हैfoldl' f'
हमार

43

पर अनुभाग Thunks और कमजोर हेड सामान्य फार्म हास्केल विकिबुक्स में आलस्य का वर्णन इस उपयोगी चित्रण के साथ WHNF का एक बहुत अच्छा विवरण प्रदान करता है:

मूल्य का मूल्यांकन (4, [1, 2]) चरण दर चरण।  पहला चरण पूरी तरह से निर्विवाद है;  बाद के सभी फॉर्म WHNF में हैं, और अंतिम भी सामान्य रूप में है।

मूल्य का मूल्यांकन (4, [1, 2]) चरण दर चरण। पहला चरण पूरी तरह से निर्विवाद है; बाद के सभी फॉर्म WHNF में हैं, और अंतिम भी सामान्य रूप में है।


5
मुझे पता है कि लोग सिर को सामान्य रूप से अनदेखा करने के लिए कहते हैं, लेकिन क्या आप उस आरेख में एक उदाहरण दे सकते हैं जो आपके पास सामान्य सामान्य रूप में दिखता है?
CMCDragonkai

28

हास्केल कार्यक्रम अभिव्यक्ति हैं और वे मूल्यांकन प्रदर्शन करके चलाए जाते हैं ।

एक अभिव्यक्ति का मूल्यांकन करने के लिए, सभी फ़ंक्शन अनुप्रयोगों को उनकी परिभाषाओं द्वारा प्रतिस्थापित करें। जिस क्रम में आप ऐसा करते हैं वह ज्यादा मायने नहीं रखता है, लेकिन यह अभी भी महत्वपूर्ण है: सबसे बाहरी अनुप्रयोग से शुरू करें और बाएं से दाएं आगे बढ़ें; इसे आलसी मूल्यांकन कहा जाता है ।

उदाहरण:

   take 1 (1:2:3:[])
=> { apply take }
   1 : take (1-1) (2:3:[])
=> { apply (-)  }
   1 : take 0 (2:3:[])
=> { apply take }
   1 : []

जब कोई अधिक फ़ंक्शन अनुप्रयोग को प्रतिस्थापित करने के लिए शेष नहीं हैं, तो मूल्यांकन बंद हो जाता है। इसका परिणाम सामान्य रूप में होता है (या सामान्य रूप में कमी , आरएनएफ)। कोई फर्क नहीं पड़ता कि आप किस क्रम में एक अभिव्यक्ति का मूल्यांकन करते हैं, आप हमेशा एक ही सामान्य रूप के साथ समाप्त होंगे (लेकिन केवल यदि मूल्यांकन समाप्त हो जाता है)।

आलसी मूल्यांकन के लिए थोड़ा अलग वर्णन है। अर्थात्, यह कहता है कि आपको केवल कमजोर सामान्य रूप में सब कुछ का मूल्यांकन करना चाहिए । WHNF में अभिव्यक्ति के लिए ठीक तीन मामले हैं:

  • एक निर्माता: constructor expression_1 expression_2 ...
  • एक अंतर्निहित फ़ंक्शन बहुत कम तर्कों के साथ, जैसे (+) 2याsqrt
  • एक लंबोदर-अभिव्यक्ति: \x -> expression

दूसरे शब्दों में, अभिव्यक्ति के प्रमुख (यानी सबसे बाहरी फ़ंक्शन अनुप्रयोग) का मूल्यांकन किसी भी आगे नहीं किया जा सकता है, लेकिन फ़ंक्शन तर्क में अविकसित अभिव्यक्ति हो सकती है।

WHNF के उदाहरण:

3 : take 2 [2,3,4]   -- outermost function is a constructor (:)
(3+1) : [4..]        -- ditto
\x -> 4+5            -- lambda expression

टिप्पणियाँ

  1. WHNF में "हेड" एक सूची के प्रमुख को संदर्भित नहीं करता है, लेकिन सबसे बाहरी फ़ंक्शन एप्लिकेशन को।
  2. कभी-कभी, लोग अविकसित अभिव्यक्तियों को "थ्रक्स" कहते हैं, लेकिन मुझे नहीं लगता कि यह समझने का एक अच्छा तरीका है।
  3. हेड नॉर्मल फॉर्म (HNF) हास्केल के लिए अप्रासंगिक है। यह WHNF से भिन्न है कि लैम्ब्डा अभिव्यक्तियों के शरीर का मूल्यांकन कुछ हद तक किया जाता है।

क्या WHNF से HNF तक के मूल्यांकन seqमें foldl'बल का उपयोग होता है ?

1
@snmcdonald: नहीं, Haskell HNF का उपयोग नहीं करता है। मूल्यांकन दूसरी अभिव्यक्ति का मूल्यांकन करने से पहले WHNF को seq expr1 expr2पहली अभिव्यक्ति का मूल्यांकन करेगा । expr1expr2
हेनरिक एपेल्मस

26

उदाहरणों के साथ एक अच्छी व्याख्या http://foldoc.org/Weak+Head+Normal+Form पर दी गई है, जो सामान्य रूप से एक फ़ंक्शन एब्सट्रैक्शन के अंदर एक अभिव्यक्ति के बिट्स को भी सरल बनाती है, जबकि "कमजोर" सिर सामान्य रूप फ़ंक्शन एब्स्ट्रक्शन पर रुक जाता है ।

स्रोत से, यदि आपके पास:

\ x -> ((\ y -> y+x) 2)

यह कमजोर सिर सामान्य रूप में है, लेकिन सिर सामान्य रूप में नहीं ... क्योंकि संभव अनुप्रयोग एक फ़ंक्शन के अंदर फंस गया है जिसका अभी मूल्यांकन नहीं किया जा सकता है।

वास्तविक सिर सामान्य रूप से कुशलतापूर्वक लागू करना मुश्किल होगा। यह कार्यों के अंदर चारों ओर poking की आवश्यकता होगी। तो कमजोर सिर के सामान्य रूप का लाभ यह है कि आप अभी भी एक अपारदर्शी प्रकार के रूप में कार्यों को लागू कर सकते हैं, और इसलिए यह संकलित भाषाओं और अनुकूलन के साथ अधिक संगत है।


12

WHNF नहीं चाहता कि लंबोदा के शरीर का मूल्यांकन किया जाए, इसलिए

WHNF = \a -> thunk
HNF = \a -> a + c

seq चाहता है कि इसका पहला तर्क WHNF में हो, इसलिए

let a = \b c d e -> (\f -> b + c + d + e + f) b
    b = a 2
in seq b (b 5)

का मूल्यांकन करता है

\d e -> (\f -> 2 + 5 + d + e + f) 2

इसके बजाय, HNF का उपयोग क्या होगा

\d e -> 2 + 5 + d + e + 2

या मैं उदाहरण को गलत समझता हूं, या आप WHNF और HNF में 1 और 2 मिलाते हैं।
जेन

5

मूल रूप से, मान लीजिए कि आपके पास किसी प्रकार का थन है t,।

अब, यदि हम tWHNF या NHF का मूल्यांकन करना चाहते हैं , जो कार्यों को छोड़कर समान हैं, तो हम पाएंगे कि हमें कुछ ऐसा मिलता है

t1 : t2जहां t1और t2हैं। इस मामले में, t1आपका 0(या बल्कि, 0कोई अतिरिक्त अनबॉक्सिंग करने के लिए एक थंक होगा )

seqऔर $!WHNF को विकसित करना। ध्यान दें कि

f $! x = seq x (f x)

1
@snmcdonald HNF पर ध्यान न दें। seq का कहना है कि जब यह WHNF के लिए मूल्यांकन किया जाता है, तो WHNF के पहले तर्क का मूल्यांकन करें।
वैकल्पिक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.