कार्यात्मक प्रोग्रामिंग में, अधिकांश डेटा संरचनाओं को अपरिवर्तनीय रखने के लिए अधिक मेमोरी उपयोग की आवश्यकता होती है?


63

कार्यात्मक प्रोग्रामिंग में चूंकि लगभग सभी डेटा संरचना अपरिवर्तनीय हैं, जब राज्य को एक नया ढांचा बदलना होता है। क्या इसका मतलब बहुत अधिक मेमोरी उपयोग है? मैं ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग प्रतिमान को अच्छी तरह से जानता हूं, अब मैं कार्यात्मक प्रोग्रामिंग प्रतिमान के बारे में जानने की कोशिश कर रहा हूं। सब कुछ अपरिवर्तनीय होने की अवधारणा मुझे भ्रमित करती है। ऐसा लगता है कि अपरिवर्तनीय संरचनाओं का उपयोग करने वाले कार्यक्रम को उत्परिवर्तित संरचनाओं के साथ प्रोग्राम की तुलना में बहुत अधिक मेमोरी की आवश्यकता होगी। क्या मैं भी इसे सही तरीके से देख रहा हूं?


7
इसका मतलब यह हो सकता है, लेकिन अधिकांश अपरिवर्तनीय डेटा संरचनाएं परिवर्तनों के लिए अंतर्निहित डेटा का पुन: उपयोग करती हैं। एरिक लिपर्ट के पास
Oded

3
मैं विशुद्ध रूप से कार्यात्मक डेटा संरचनाओं पर एक नज़र डालूंगा, यह एक महान पुस्तक है जो उसी व्यक्ति द्वारा लिखी गई है जिसने हास्केल के अधिकांश कंटेनर लाइब्रेरी (हालांकि किताब मुख्य रूप से एसएमएल है) को लिखा है
jozefg

1
मेमोरी खपत के बजाय रनिंग टाइम से संबंधित यह उत्तर आपके लिए भी दिलचस्प हो सकता है: stackoverflow.com/questions/1990464/…
9000

1
आपको यह दिलचस्प लग सकता है: en.wikipedia.org/wiki/Static_single_assignment_form
शॉन मैकसमर्थी

जवाबों:


35

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


24

कार्यात्मक प्रोग्रामिंग में चूंकि लगभग सभी डेटा संरचना अपरिवर्तनीय हैं, जब राज्य को एक नया ढांचा बदलना होता है। क्या इसका मतलब बहुत अधिक मेमोरी उपयोग है?

यह डेटा संरचना, आपके द्वारा किए गए सटीक परिवर्तनों और, कुछ मामलों में, ऑप्टिमाइज़र पर निर्भर करता है। एक उदाहरण के रूप में आइए एक सूची पर विचार करें:

list2 = prepend(42, list1) // list2 is now a list that contains 42 followed
                           // by the elements of list1. list1 is unchanged

यहां अतिरिक्त मेमोरी आवश्यकता स्थिर है - इसलिए कॉलिंग की रनटाइम लागत है prepend। क्यों? क्योंकि prependबस एक नई कोशिका का निर्माण होता है 42जिसका सिर और list1उसकी पूंछ के रूप में होता है। इसे list2प्राप्त करने के लिए इसे कॉपी या अन्यथा पुनरावृति नहीं करना पड़ता है । अर्थात्, स्टोर करने के लिए आवश्यक मेमोरी को छोड़कर 42, list2उसी मेमोरी का पुन: उपयोग करता है जिसका उपयोग किया जाता है list1। चूंकि दोनों सूचियां अपरिवर्तनीय हैं, इसलिए यह साझाकरण पूरी तरह से सुरक्षित है।

इसी प्रकार, संतुलित वृक्ष संरचनाओं के साथ काम करते समय, अधिकांश परिचालनों के लिए केवल अतिरिक्त स्थान की एक लघु राशि की आवश्यकता होती है क्योंकि सब कुछ, लेकिन पेड़ का एक मार्ग साझा किया जा सकता है।

सरणियों के लिए स्थिति थोड़ी अलग है। यही कारण है कि, कई एफपी भाषाओं में, सरणियां आमतौर पर उपयोग नहीं की जाती हैं। हालाँकि, यदि आप ऐसा कुछ करते हैं arr2 = map(f, arr1)और arr1इस पंक्ति के बाद फिर कभी उपयोग नहीं किया जाता है, तो एक स्मार्ट ऑप्टिमाइज़र वास्तव में arr1एक नया सरणी बनाने (प्रोग्राम के व्यवहार को प्रभावित किए बिना) के बजाय उत्परिवर्तन कोड बना सकता है । उस स्थिति में प्रदर्शन अनिवार्य रूप से अनिवार्य भाषा में होगा।


1
ब्याज के बाहर, किन भाषाओं के कार्यान्वयन ने अंतरिक्ष का पुन: उपयोग किया जैसा कि आपने अंत में बताया?

@delnan मेरे विश्वविद्यालय में क्यूब नामक एक शोध भाषा थी, जिसने ऐसा किया। मैं नहीं जानता कि वहाँ किसी भी इस्तेमाल किया-में-जंगली भाषा है कि यह करता है, हालांकि है। हालाँकि हास्केल का संलयन कई मामलों में समान प्रभाव प्राप्त कर सकता है।
sepp2k

7

Naive कार्यान्वयन वास्तव में इस समस्या को उजागर करेगा - जब आप किसी मौजूदा एक को अपडेट करने के बजाय एक नई डेटा संरचना बनाते हैं, तो आपको कुछ ओवरहेड करना होगा।

विभिन्न भाषाओं में इससे निपटने के अलग-अलग तरीके होते हैं, और उनमें से कुछ तरकीबें होती हैं।

एक रणनीति कचरा संग्रहण है । जिस समय नई संरचना बनाई गई है, या कुछ ही समय बाद, पुरानी संरचना के संदर्भ दायरे से बाहर हो गए हैं, और जीसी एल्गोरिथ्म के आधार पर कचरा कलेक्टर इसे तुरंत या बहुत जल्द उठाएगा। इसका मतलब यह है कि जहां अभी भी एक ओवरहेड है, यह केवल अस्थायी है, और डेटा की मात्रा के साथ रैखिक रूप से नहीं बढ़ेगा।

एक अन्य डेटा संरचनाओं के विभिन्न प्रकार उठा रहा है। जहाँ सरणियाँ अनिवार्य भाषाओं में डेटा संरचना को सूचीबद्ध करने के लिए होती हैं (आमतौर पर किसी प्रकार के डायनेमिक-रियलाइजेशन कंटेनर जैसे कि std::vectorC ++ में लिपटे हुए ), कार्यात्मक भाषाएं अक्सर लिंक की गई सूचियों को पसंद करती हैं। लिंक की गई सूची के साथ, एक प्रीपेन्ड ऑपरेशन ('विपक्ष') नई सूची की पूंछ के रूप में मौजूदा सूची का पुन: उपयोग कर सकता है, इसलिए जो वास्तव में आवंटित किया जाता है वह नई सूची का प्रमुख होता है। अन्य प्रकार की डेटा संरचनाओं के लिए समान रणनीति मौजूद है - सेट, पेड़, आप इसे नाम देते हैं।

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


वाह, एक छोटा सा जवाब और इतनी जानकारी / अंतर्दृष्टि। धन्यवाद :)
गेरी

3

मैं केवल क्लोजर के बारे में थोड़ा जानता हूं और यह अपरिवर्तनीय डेटा संरचनाएं हैं

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

आलेखीय रूप से, हम कुछ इस तरह का प्रतिनिधित्व कर सकते हैं:

(def my-list '(1 2 3))

    +---+      +---+      +---+
    | 1 | ---> | 2 | ---> | 3 |
    +---+      +---+      +---+

(def new-list (conj my-list 0))

              +-----------------------------+
    +---+     | +---+      +---+      +---+ |
    | 0 | --->| | 1 | ---> | 2 | ---> | 3 | |
    +---+     | +---+      +---+      +---+ |
              +-----------------------------+

2

अन्य उत्तरों में जो कहा गया है, इसके अलावा, मैं क्लीन प्रोग्रामिंग भाषा का उल्लेख करना चाहूंगा, जो तथाकथित अद्वितीय प्रकारों का समर्थन करती है । मुझे इस भाषा का पता नहीं है, लेकिन मुझे लगता है कि यह अनोखा प्रकार "विनाशकारी अद्यतन" का समर्थन करता है।

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

अधिक जानकारी के लिए, उदाहरण के लिए स्वच्छ मुखपृष्ठ और इस विकिपीडिया लेख को देखें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.