Recursion Schemes Explained Using Regular Expressions | by Walter Schulze | Nov, 2022

कैटमॉर्फिज्म और पैरामोर्फिज्म के लिए एक ट्यूटोरियल

द्वारा तसवीर जेम्स हैरिसन पर unsplash

पुनरावर्तन योजनाएँ पुनरावृत्ति को दूर करने का एक तरीका है। कुछ ने तर्क दिया है पुनरावर्तन योजनाओं के बिना कार्यात्मक प्रोग्रामिंग अनिवार्य प्रोग्रामिंग के बराबर है for लूप्स, बल्कि साथ goto बयान।

उपयोग के रूप में while तथा for बजाय लूप्स goto अनिवार्य नियंत्रण प्रवाह के लिए संरचना और सद्भाव लाता है, हस्तलिखित रिकर्सन पर रिकर्सन योजनाओं का उपयोग रिकर्सिव कंप्यूटेशंस के समान संरचना लाता है। यह अंतर्दृष्टि इतनी महत्वपूर्ण है कि मैं इसे दोहराऊंगा: पुनरावर्तन योजनाएँ मुहावरेदार कार्यात्मक प्रोग्रामिंग के लिए उतनी ही आवश्यक हैं जितनी कि for तथा while मुहावरेदार अनिवार्य प्रोग्रामिंग हैं। — पैट्रिक थॉम्पसन

मेरी इतनी मजबूत राय नहीं है। मुझे लगता है कि पुनरावर्तन योजनाएँ मन उड़ाने वाली दिलचस्प हैं, और मैं इससे पक्षपाती भी हूँ पैट्रिक बह्र ने ट्री ऑटोमेटा के साथ जो संबंध बनाया चूँकि मैंने ट्री ऑटोमेटा के साथ काफी काम किया है, और इसे व्यवहार में लागू होते देखना आपके लिए जितना रोमांचक होगा, उससे कहीं अधिक मेरे लिए रोमांचक है।

जेरी के गोल्फ खेल से दो स्ट्रोक लेने के असंभव कार्य को पूरा करने के लिए मिस्टर मीसीक्स ने मिस्टर मीसीक्स को बार-बार बुलाया – जैसा कि देखा गया है रिक और मोर्टी

हमारे द्वारा उपयोग किए जाने वाले पुनरावर्तन के विपरीत, ये पुनरावर्तन योजनाएँ हमें उस प्रकार के पुनरावर्तन के बारे में बहुत विशिष्ट होने से रोकती हैं जिसे हम अपने कार्य में लागू करेंगे। पुनरावर्तन योजनाएँ पठनीयता (अंततः) में सहायता करती हैं और पुनरावर्ती मूल्यांकन कार्यों के सरल कार्यान्वयन की अनुमति देती हैं, लेकिन आपके डेटा प्रकारों को परिभाषित करते समय कुछ अतिरिक्त बॉयलरप्लेट की आवश्यकता होती है और उनका उपयोग करने के तरीके सीखने के लिए थोड़े अभ्यास की आवश्यकता होती है।

रिकर्सन कई प्रकार के होते हैं, इसलिए उन सभी को कवर करने के लिए हमारे पास कुछ योजनाएँ हैं। इस लेख में, मैं दो सबसे आम पुनरावर्तन योजनाओं की व्याख्या करने की कोशिश करूँगा:

  • कैटामॉर्फिज्म: एक साधारण बॉटम-अप रिकर्सन के लिए एक फैंसी नाम।
  • पैरामॉर्फिज्म: थोड़ा स्मार्ट बॉटम-अप रिकर्सन के लिए एक और फैंसी नाम जिसमें कुछ स्टैक जानकारी की आवश्यकता होती है। अभी भी काफी सरल है।

टिप्पणी: इस लेख के लिए हास्केल की समझ की आवश्यकता है, कम से कम एक Functor को लागू करने तक।

पुनरावर्तन योजनाओं की व्याख्या करने के लिए एक उदाहरण की आवश्यकता होती है। इसके लिए, मैंने रेगुलर एक्सप्रेशंस के लिए डेरिवेटिव एल्गोरिथम चुना है, जिसे मैंने कवर किया है एक पिछला लेख.

इस पोस्ट के लिए, मैं सामान्य पुनरावर्तन का उपयोग करके केवल कार्यान्वयन कोड शामिल करूंगा ताकि हम देख सकें कि पुनरावर्तन योजनाओं को जोड़ने से कैसे फर्क पड़ेगा। यहाँ पूर्ण रेगुलर एक्सप्रेशन मिलान एल्गोरिथम है जो हमारे द्वारा उपयोग किए जाने वाले पुनरावर्तन का उपयोग करता है।

व्युत्पन्न या deriv इनपुट के रूप में एक वर्ण दिए जाने पर, फ़ंक्शन मिलान के लिए छोड़ी गई नियमित अभिव्यक्ति लौटाता है। हम इसे इनपुट स्ट्रिंग का उपयोग करके लूप करते हैं foldl, नियमित अभिव्यक्ति प्राप्त करने के लिए जो पूरी स्ट्रिंग का उपभोग करने के बाद मिलान करने के लिए छोड़ दिया गया है। फिर हम जाँचते हैं कि परिणामी रेगुलर एक्सप्रेशन का उपयोग करके खाली स्ट्रिंग से मेल खाता है या नहीं nullable फ़ंक्शन, यह जानने के लिए कि क्या रेगेक्स जिसने पूरे स्ट्रिंग का उपभोग किया है, उस स्ट्रिंग से मेल खाता है।

तकनीकी रूप से, foldl पहले से ही कुछ पुनरावर्तन को अमूर्त कर दिया गया है, लेकिन हम पुनरावर्तन योजनाओं के साथ इस अमूर्तता को दूसरे स्तर पर ले जा रहे हैं।

इससे पहले कि हम बहुत गहराई में जाएँ, आइए देखें कि हम कहाँ जा रहे हैं। nullable फ़ंक्शन एक पूर्ण कैटमोर्फिज्म है क्योंकि यह केवल नीचे-ऊपर रिकर्सन करता है। बॉटम-अप रिकर्सन तब होता है जब किसी फ़ंक्शन का परिणाम केवल उसके बच्चों पर लागू किए गए पुनरावर्ती फ़ंक्शन के परिणामों पर निर्भर करता है, न कि पुनरावर्ती फ़ंक्शन में दिए गए किसी मध्यवर्ती परिणाम पर। हम अपनी जगह लेने जा रहे हैं nullable निम्नलिखित समतुल्य फ़ंक्शन के साथ उपरोक्त मूल कार्यान्वयन में कार्य करें:

ध्यान दें कि कैसे nullableAlg फ़ंक्शन को स्वयं को कोई पुनरावर्ती कॉल करने की आवश्यकता नहीं है। इस लेख में, हम बताएंगे कि यह कैसे संभव है और साथ ही:

  • क्या एक FAlgebra है
  • एक नया प्रकार क्यों कहा जाता है RegexF
  • कैसे cata समारोह नीचे-ऊपर रिकर्सन करता है

हमारी पुनरावर्तन योजनाओं को हमारे पुनरावर्तन के दौरान मध्यवर्ती परिणामों को संग्रहीत करने के लिए स्थान की आवश्यकता होती है। हमें इन परिणामों के लिए एक कंटेनर चाहिए। हम जानते हैं कि फ़नकार एक बेहतरीन कंटेनर है, इसलिए हम अपना टर्न करेंगे Regex पैरामीट्रिजिंग करके डेटा टाइप को फ़ंक्टर में बदलें। यह पैरामीटर हमारे पुनरावर्ती एल्गोरिदम के दौरान विभिन्न मध्यवर्ती परिणामों को संग्रहीत कर सकता है।

हम नाम बदलकर शुरू करते हैं Regex प्रति RegexF जहां F functor के लिए खड़ा है। हम सभी रिकर्सिव को पैरामीट्रिज करते हैं Regex खेत। यहाँ कोड है:

यदि आप वापस जाते हैं और की मूल परिभाषा को देखते हैं Regex आप देखेंगे कि हमने की सभी पुनरावर्ती घटनाओं को बदल दिया है Regex साथ r. हमने पैरामीटर का नाम दिया r पुनरावर्ती या परिणाम के लिए। इस r बूलियन परिणामों को संग्रहीत करने के लिए उपयोग किया जाएगा क्योंकि हम कैटामोर्फिज्म का उपयोग करके अपने अशक्त कार्य के लिए बॉटम-अप रिकर्सन करते हैं, लेकिन जल्द ही उस पर और अधिक।

सबसे पहले, हमें समस्या है। हमें यह चुनना होगा कि कौन सा पैरामीटर सेट करना है r करने के लिए, हमारे मूल प्राप्त करने के लिए Regex वापस की परिभाषा का उपयोग कर RegexF.

  • अगर हम बनाते हैं r एक Boolहम केवल बहुत ही छोटे व्यंजक बना सकते हैं जैसे EmptyStringलेकिन अन्य इससे भी कम अर्थ निकालते हैं: Concat True False.
  • अगर हम चुनते हैं r होना Regex हम पाते हैं RegexF Regexजो काम करेगा, लेकिन हम अपनी रिकर्सिव फ़ैक्टर संपत्ति खो देते हैं।
  • हम चुनने की कोशिश कर सकते हैं r होना RegexFतो हमें मिलता है RegexF RegexFयह करीब है, लेकिन अब हमें दूसरे की आवश्यकता है RegexF दूसरे के लिए पैरामीटर होना RegexFतो हम प्राप्त करते हैं RegexF (RegexF RegexF), लेकिन यह कभी न खत्म होने वाली समस्या है। किसी बिंदु पर, हम चाहते हैं कि यह पुनरावर्तन समाप्त हो।
  • हम दो स्तरों की कोशिश कर सकते हैं RegexFतो हमें मिलता है RegexF (RegexF (RegexF ()))लेकिन तब हम केवल दो स्तरों की गहराई के नियमित भावों का प्रतिनिधित्व कर सकते हैं।

हम एक मज़ेदार चाहते हैं, लेकिन हम अधिकतम रिकर्सन गहराई तक सीमित नहीं होना चाहते हैं।

फिक्स-इट फेलिक्स जूनियर जेल में है, जहां अगर आप बचना चाहते हैं तो चीजों को ठीक करना बहुत उपयोगी नहीं है – जैसा कि इसमें देखा गया है रेक इट रैल्फ

एक निश्चित बिंदु वह होता है जहां एक फ़ंक्शन अभिसरण करता है या जहां फ़ंक्शन का इनपुट आउटपुट के बराबर होता है। उदाहरण के लिए: f(x) = x² का एक निश्चित बिंदु 1 है क्योंकि 1² = 1 है।

हम अपने समकक्ष को फिर से बना सकते हैं Regex डेटा प्रकार एक निश्चित बिंदु का उपयोग कर:

type Regex = Fix RegexF

यह कैसा है Fix RegexF हमारे मूल के बराबर Regex?
इसे समझने के लिए, हमें बारीकी से देखने की जरूरत है Fix. Fix एक निश्चित बिंदु डेटा प्रकार है:

newtype Fix f = Fix (f (Fix f))

मुझे इसके चारों ओर अपना सिर लपेटने में काफी समय लगा, लेकिन आखिरकार, मेरा दिमाग एक निश्चित बिंदु पर पहुंच गया।

लेकिन अगर आप प्रत्येक टुकड़े पर एक शांत नज़र डालें, तो यह समझ में आ सकता है:

newtype Fix f = Fix (f (Fix f))
  1. सबसे पहला Fix प्रकार का नाम है।
  2. दूसरा Fix प्रकार निर्माता नाम है।
  3. तीसरा Fix प्रकार का प्रयोग किया जा रहा है।

Fix प्रकार एक प्रकार का पैरामीटर लेता है जिसे कहा जाता है f. f एक मज़ेदार है, जिसका अर्थ है कि यह एक प्रकार का पैरामीटर भी लेता है। इस स्थिति में, प्रकार पैरामीटर अंतिम होगा Fix f.

अगर हम चुनते हैं RegexF जैसा हमारा fहमारा प्रकार पैरामीटर r होगा Fix RegexFलेकिन हम यह जानते हैं Fix RegexF सच है Fix (RegexF (Fix RegexF))जो हम जानते हैं वास्तव में है Fix (RegexF (Fix (RegexF (Fix RegexF)))), आदि। यह वही है जो हम चाहते थे। अब हम किसी भी गहराई के नियमित भावों का प्रतिनिधित्व कर सकते हैं। फेलिक्स ने इसे ठीक किया!

रेक इट रैल्फ हमारे को अनफिक्स भी कर सकता है Fix डेटा प्रकार, जो कैटमॉर्फिज़्म फ़ंक्शन के कार्यान्वयन में उपयोगी होगा।

wreckit एक पैटर्न से मेल खाता है Fix और अंदर मान लौटाता है:

wreckit :: Fix f -> f (Fix f)
wreckit (Fix x) = x

अब जबकि हमने अपना फंक्टर एक्सप्रेशन बना लिया है, आइए कुछ सिद्धांत को कवर करें कि हम रिकर्सन को कैसे अमूर्त करेंगे।

एक बीजगणित में निम्न शामिल हैं:

  1. भाव बनाने की क्षमता। उदाहरण के लिए, द Regex कंस्ट्रक्टर और
  2. उदाहरण के लिए, इन भावों का मूल्यांकन करने की क्षमता nullable :: Regex -> Bool

एक बीजगणित के दो भागों से मिलकर होने के बावजूद, मूल्यांकन कार्य को आमतौर पर बीजगणित कहा जाता है:

type Algebra e r = e -> r

इसका मतलब है nullable कार्य प्रभावी रूप से है NullableAlgebra

type NullableAlgebra = Algebra Regex Bool
nullable :: NullableAlgebra

श्रेणी सिद्धांत में एक एफ-बीजगणित में निम्नलिखित शामिल हैं:

  1. उदाहरण के लिए, फ़ंक्टर वाले भावों को बनाने की क्षमता RegexF r तथा
  2. इन अभिव्यक्तियों का मूल्यांकन करने की क्षमता, उदाहरण के लिए: nullable :: RegexF Bool -> Bool.

इसका मतलब है कि एफ-बीजगणित का प्रकार है:

type FAlgebra f r = f r -> r

nullableAlg समारोह है NullableAlgebra:

type NullableFAlgebra = FAlgebra RegexF Bool
nullableAlg :: NullableFAlgebra

कैसे करता है cata फ़ंक्शन सार नीचे-ऊपर रिकर्सन को दूर करता है? यहाँ पूरा समारोह है:

cata :: Functor f => FAlgebra f r -> Fix f -> r
cata alg = alg . fmap (cata alg) . wreckit

यह बहुत सारगर्भित है। आइए इसे और अधिक विशिष्ट बनाएं। पठनीयता में सहायता के लिए, पहले आइए एल्म के पाइप ऑपरेटर को हास्केल में जोड़ेंजो के बराबर है & हास्केल में ऑपरेटर, लेकिन यह यूनिक्स पाइप की तरह अधिक दिखता है और दिशा को इंगित करता है। क्षमा करें, मुझे यह पढ़ना आसान लगता है।

infixl 0 |>
(|>) :: a -> (a -> b) -> b
x |> f = f x

अब, चलिए बनाते हैं cata कार्य कार्यान्वयन के आवेदन के लिए अधिक विशिष्ट है nullableAlgका कार्य:

cata :: NullableFAlgebra -> Regex -> Bool
cata nullableAlg regex =
wreckit regex
|> fmap (cata nullableAlg)
|> nullableAlg

पहले कदम के रूप में, wreckit हमारा ले जाएगा Regex और बाहरी को हटा दें Fix:

wreckit :: Regex -> RegexF Regex

अब हमारे पास एक फ़ंक्टर है, जिसका अर्थ है कि हम कर सकते हैं fmap ऊपर RegexF. fmap एक स्तर नीचे की ओर लौटता है। जिस फ़ंक्शन के साथ हम दोबारा काम करना चाहते हैं वह है nullable समारोह जो के बराबर है cata nullableAlg.

nullable :: Regex -> Bool
nullable = cata nullableAlg

रिकर्सन के प्रत्येक स्तर पर, हम अपने को कॉल करना चाहते हैं nullable समारोह। तो हर बार हम fmap एक स्तर नीचे, हम नीचे गुजरते हैं cata nullableAlg निचले स्तर पर बुलाया जाएगा।

fmap (cata nullableAlg) :: RegexF Regex -> RegexF Bool

जैसा कि हम बैक अप की पुनरावृत्ति करते हैं, हम वापस लौटते हैं RegexF Boolजो के मध्यवर्ती परिणामों को संग्रहीत करता है nullable निचले स्तरों पर गणना। फिर हमें इसे फाइनल से गुजरना होगा nullableAlg फाइनल पाने के लिए Bool नतीजा।

nullableAlg :: RegexF Bool -> Bool

इसका मतलब है कि अब हम परिभाषित कर सकते हैं nullableAlg बिना किसी रिकर्सन के काम करता है क्योंकि cata function हमारे लिए सभी बॉटम-अप रिकर्सन करेगा nullable समारोह:

यह एक कैटमॉर्फिज्म का उपयोग करने का सिर्फ एक उदाहरण था, और कैटमोर्फिज्म सरल बॉटम-अप रिकर्सन तक सीमित है, तो चलिए एक और रिकर्सन स्कीम के बारे में सीखते हैं।

इससे पहले कि हम अपनी अगली पुनरावर्तन योजना देखें, आप इस बारे में चिंतित हो सकते हैं कि एपीआई कैसा दिखेगा। आप उपयोगकर्ताओं को कैसे समझाएंगे कि आपके पुस्तकालय के उपयोगकर्ताओं के लिए एक निश्चित बिंदु क्या है? विचार यह है कि हम इन कार्यान्वयन विवरणों को पुस्तकालय के आंतरिक भाग तक सीमित रखेंगे। हमारा एपीआई किसी भी निश्चित बिंदु को उजागर नहीं करेगा। यह उजागर नहीं करेगा nullableAlg इस पुस्तकालय के उपयोगकर्ताओं के लिए कार्य करता है, केवल nullable समारोह। वही डेटा टाइप कंस्ट्रक्टर्स के लिए जाता है। हम एक्सपोज नहीं करना चाहते Fix हमारे पुस्तकालय के बाहर, इसलिए हम स्मार्ट कंस्ट्रक्टर बनाते हैं जो उपयोगकर्ता के लिए निश्चित बिंदुओं का निर्माण करते हैं जिन्हें हम उजागर कर सकते हैं:

लागू करते समय यह हमारी अपनी आंतरिक सुविधा के लिए भी होगा deriv अगले भाग में पैरामोर्फिज्म का उपयोग करते हुए कार्य करें।

यदि आप लूप के लिए नहीं समझते हैं, तो वे एक प्राकृतिक संख्या पर एक पैरामोर्फिज्म हैं — जोसेफ स्वेनिंग्सन

आइए मूल को देखें deriv कार्य फिर से:

deriv फ़ंक्शन एक साधारण बॉटम-अप रिकर्सन नहीं है, क्योंकि Concat चरण को जाँचने की आवश्यकता है कि क्या पहली अभिव्यक्ति है r अशक्त है। यह वह जानकारी है जो केवल स्टैक पर उपलब्ध है। एक पैरामॉर्फिज्म न केवल फंक्शनल पैरामीटर में एक आंतरायिक परिणाम रखता है, बल्कि मूल अभिव्यक्ति की एक प्रति भी रखता है ताकि हम यह जांच सकें कि यह अशक्त है या नहीं। इसके लिए एक नए बीजगणित की आवश्यकता होती है जिसे a कहा जाता है RAlgebra:

type RAlgebra f r = f (Fix f, r) -> r

फ़ंक्टर में अब एक टपल होता है, जहाँ पहला पैरामीटर मूल अभिव्यक्ति की एक प्रति है और दूसरा मध्यवर्ती परिणाम है। हमारी DeriveRAlgebraथोड़ा भ्रमित करने वाला लगेगा क्योंकि हमारा इंटरमीडिएट परिणाम मूल की प्रति के समान प्रकार का है:

type DeriveRAlgebra = RAlgebra RegexF Regex :: RegexF (Regex, Regex) -> Regex

इस RAlgebra द्वारा मूल्यांकन किया जाएगा para समारोह:

para :: (Functor f) => RAlgebra f r -> Fix f -> r
para alg f =
wreckit f
|> fmap (\x -> (x, para alg x))
|> alg

हम इसे और अधिक विशिष्ट बना सकते हैं derivAlg समारोह:

para :: DeriveRAlgebra -> Regex -> Regex
para derivAlg regex =
wreckit regex
|> fmap (\x -> (x, para derivAlg x))
|> derivAlg

से फर्क सिर्फ इतना है cata कार्य मूल की प्रति का भंडारण है Regex, x टपल में, न केवल मध्यवर्ती परिणाम, para derivAlg x.

  1. हम इसकी बाहरी परत को हटा देते हैं Fix का उपयोग करते हुए wreckit :: Regex -> RegexF Regex.
  2. हम उपयोग करने वाले मज़ेदार के एक स्तर को दोबारा शुरू करते हैं fmap.
  3. निचले स्तरों पर हम जो कार्य लागू करते हैं वह एक लेता है Regex और मूल लौटाता है Regex साथ ही व्युत्पन्न Regex : Regex -> (Regex, Regex)
  4. परिणाम एक functor में है, RegexF (Regex, Regex)जिसका उपयोग करके हम मूल्यांकन कर सकते हैं: derivAlg c :: RegexF (Regex, Regex) -> Regex हमारे अंतिम व्युत्पन्न प्राप्त करने के लिए Regex.

इसका मतलब है कि अब हम परिभाषित कर सकते हैं derivAlg बिना किसी पुनरावर्तन के कार्य करता है, क्योंकि para function हमारे लिए सभी रिकर्सन कार्य करेगा deriv समारोह:

आप देख सकते हैं कि हमें इनपुट पैरामीटर के चारों ओर स्वैप करने की आवश्यकता है derivAlg इसे काम करने के लिए कार्य करें।

ये पुनरावर्तन योजनाओं का उपयोग करने के केवल दो उदाहरण थे, लेकिन यह हमारे एल्गोरिथम को पूरा करने के लिए पर्याप्त है:

आप संपूर्ण पा सकते हैं GitHub पर डेमो.

हमने केवल दो पुनरावर्ती योजनाओं को कवर किया है, लेकिन कई अन्य हैं:

  • के आलावा para तथा cataएक और तह कहा जाता है histo. हिस्टोमॉर्फिज्म सभी पुनरावर्ती गणनाओं के इतिहास को संरक्षित करता है, जो एल्गोरिदम के लिए उपयोगी है, जिसके लिए फिबोनाची की तरह दक्षता के लिए मेमोइज़ेशन की आवश्यकता होती है।
  • अनफोल्ड, शामिल करें ana, apo तथा futu. एनामॉर्फिज्म कैटमॉरपिज्म की श्रेणी थ्योरी डुअल है। यूनानी में, cata विनाश का मतलब है, जबकि ana मतलब बिल्डिंग। एक एनामॉर्फिज्म पुनरावर्ती रूप से एक अभिव्यक्ति का निर्माण कर सकता है, उदाहरण के लिए दी गई लंबाई के साथ शून्य की सूची बनाना।
  • फिर रिफॉल्ड्स भी हैं: hyloजो कि है cata एक के बाद ana तथा chronoजो कि है histo और ए futu.

यदि आप पुनरावर्तन योजनाओं के बारे में अधिक जानने में रुचि रखते हैं तो मैंने संदर्भ अनुभाग में कुछ संसाधनों को जोड़ा है ताकि आप अपने स्वयं के महाकाव्य रोमांच को मॉर्फ के साथ जारी रख सकें:

जब मैं बच्चा था तो मैंने टीवी पर क्या देखा: मोर्फ
  • एंडोर पेन्ज़ेस अन्य सभी पुनरावर्तन योजनाओं को प्रूफरीडिंग और समझाने के लिए, खासकर जब से मैं अभी भी कुछ ही समझता हूं।
  • मैक्स हाइबर गलती खोजने और बॉटम-अप रिकर्सन की बेहतर व्याख्या का सुझाव देने के लिए।

Leave a Reply