अपरिवर्तनीय संरचनाएं और गहरी रचना पदानुक्रम


9

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

सादगी की खातिर, मैं निम्नलिखित उदाहरण का उपयोग करूंगा - आवेदन बहुभुज आकृतियों को संपादित करने के लिए उपयोग किया जाता है, इसलिए मेरे पास "बहुभुज" वस्तु है, जो बस अपरिवर्तनीय बिंदुओं की सूची है:

Scene -> Polygon -> Point

और इसलिए मेरे कार्यक्रम में केवल एक ही परिवर्तनशील चर है - वह जो वर्तमान दृश्य वस्तु रखता है। समस्या जो मुझे तब शुरू होती है जब मैं पॉइंट ड्रैगिंग को लागू करने की कोशिश करता हूं - म्यूटेबल संस्करण में, मैं बस एक Pointऑब्जेक्ट को पकड़ता हूं और इसके निर्देशांक को संशोधित करना शुरू करता हूं । अपरिवर्तनीय संस्करण में - मैं फंस गया हूं। मैं Polygonकरंट के इंडेक्स को स्टोर कर सकता था Scene, घसीट के इंडेक्स को Polygonहर बार बदल सकता था। लेकिन यह दृष्टिकोण पैमाने पर नहीं है - जब रचना का स्तर 5 और आगे जाता है, तो बॉयलरप्लेट असहनीय हो जाएगा।

मुझे यकीन है कि इस समस्या को हल किया जा सकता है - आखिरकार, पूरी तरह से अपरिवर्तनीय संरचनाओं और आईओ मोनड के साथ हास्केल है। लेकिन मैं अभी कैसे नहीं मिल सकता है।

क्या आप मुझे संकेत दे सकते हैं?


@ जोब - यह है कि यह अभी कैसे काम करता है, और यह मुझे कई दर्द देता है। इसलिए मैं वैकल्पिक तरीकों की तलाश कर रहा हूं - और अपरिहार्यता इस एप्लिकेशन संरचना के लिए एकदम सही लगती है, कम से कम इससे पहले कि हम इसमें उपयोगकर्ता सहभागिता जोड़ते हैं :)
Rogach

@Rogach: क्या आप अपने बॉयलरप्लेट कोड के बारे में अधिक बता सकते हैं?
rwong

जवाबों:


9

मैं वर्तमान दृश्य में बहुभुज के सूचकांकों को संग्रहीत कर सकता था, बहुभुज में खींचे गए बिंदु का सूचकांक और हर बार इसे बदल सकता हूं। लेकिन यह दृष्टिकोण पैमाना नहीं है - जब रचना का स्तर 5 और आगे जाता है, तो बॉयलरप्लेट असहनीय हो जाएगा।

यदि आप बॉयलरप्लेट के आसपास नहीं पहुंच सकते तो आप बिल्कुल सही हैं । विशेष रूप से, एक छोटे सबपार्टर के साथ एक नया दृश्य बनाने के लिए बॉयलरप्लेट बदल गया। हालांकि, कई कार्यात्मक भाषाएं इस तरह के नेस्टेड संरचना हेरफेर से निपटने के लिए एक निर्माण प्रदान करती हैं: लेंस।

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

thirdItemLens :: Lens [a] a

उस प्रकार का अर्थ है कि बड़ा ढांचा चीजों की एक सूची है, और छोटा सा हिस्सा उन चीजों में से एक है। इस लेंस को देखते हुए, आप सूची में तीसरा आइटम देख और सेट कर सकते हैं:

> view thirdItemLens [1, 2, 3, 4, 5]
3
> set thirdItemLens 100 [1, 2, 3, 4, 5]
[1, 2, 100, 4, 5]

कारण लेंस उपयोगी हैं क्योंकि वे गेटर्स और सेटर का प्रतिनिधित्व करने वाले मूल्य हैं , और आप उन्हें उसी तरह से सार कर सकते हैं जिस तरह से आप अन्य मूल्यों को देख सकते हैं। आप फ़ंक्शंस कर सकते हैं जो लेंस लौटाते हैं, उदाहरण के लिए एक listItemLensफ़ंक्शन जो एक नंबर लेता है nऔर nएक सूची में वें आइटम को देखने वाले लेंस को वापस करता है । इसके अतिरिक्त, लेंस की रचना की जा सकती है :

> firstLens = listItemLens 0
> thirdLens = listItemLens 2
> firstOfThirdLens = lensCompose firstLens thirdLens
> view firstOfThirdLens [[1, 2], [3, 4], [5, 6], [7, 8]]
5
> set firstOfThirdLens 100 [[1, 2], [3, 4], [5, 6], [7, 8]]
[[1, 2], [3, 4], [100, 6], [7, 8]]

प्रत्येक लेंस डेटा संरचना के एक स्तर को पार करने के लिए व्यवहार को एनकैप्सुलेट करता है। उन्हें जोड़कर, आप जटिल संरचनाओं के कई स्तरों को पार करने के लिए बॉयलरप्लेट को समाप्त कर सकते हैं। उदाहरण के लिए, मान लें scenePolygonLens iकि आपके पास एक दृश्य में iवें बहुभुज है, और एक बहुभुज में बिंदु को polygonPointLens nदेखता है nth, तो आप एक लेंस निर्माणकर्ता को केवल उस विशिष्ट दृश्य पर ध्यान केंद्रित करने के लिए बना सकते हैं, जिसकी आप पूरे दृश्य में देखभाल करते हैं:

scenePointLens i n = lensCompose (polygonPointLens n) (scenePolygonLens i)

अब मान लीजिए कि कोई उपयोगकर्ता बहुभुज 14 के बिंदु 3 पर क्लिक करता है और इसे 10 पिक्सेल दाईं ओर घुमाता है। आप अपने दृश्य को इस तरह अपडेट कर सकते हैं:

lens = scenePointLens 14 3
point = view lens currentScene
newPoint = movePoint 10 0 point
newScene = set lens newPoint currentScene

यह अच्छी तरह से अंदर एक दृश्य lensको बदलने और अद्यतन करने के लिए सभी बॉयलरप्लेट में शामिल है , आपको केवल इस बात की परवाह करनी है कि आप किस बिंदु को बदलना चाहते हैं। आप एक साथ इस सार को आगे कर सकते हैं lensTransformसमारोह है कि एक लेंस, लक्ष्य, और एक को स्वीकार करता है समारोह लेंस के माध्यम से लक्ष्य को ध्यान में रखते अद्यतन करने के लिए:

lensTransform lens transformFunc target =
  current = view lens target
  new = transformFunc current
  set lens new target

यह एक फ़ंक्शन लेता है और इसे एक जटिल डेटा संरचना पर "अपडेटर" में बदल देता है, फ़ंक्शन को केवल दृश्य पर लागू करता है और एक नया दृश्य बनाने के लिए इसका उपयोग करता है। तो वापस 14 वें बहुभुज के तीसरे बिंदु को दाईं ओर 10 पिक्सेल तक ले जाने के परिदृश्य में जा रहा है, जिसे इस प्रकार व्यक्त किया जा सकता lensTransformहै:

lens = scenePointLens 14 3
moveRightTen point = movePoint 10 0 point
newScene = lensTransform lens moveRightTen currentScene

और बस आपको पूरे दृश्य को अपडेट करना होगा। यह एक बहुत शक्तिशाली विचार है और बहुत अच्छी तरह से काम करता है जब आपके पास अपने डेटा के टुकड़ों को देखने के लिए लेंस के निर्माण के लिए कुछ अच्छे कार्य हैं।

हालाँकि, यह सभी बहुत ही आकर्षक सामग्री है, यहाँ तक कि कार्यात्मक प्रोग्रामिंग समुदाय में भी। लेंस के साथ काम करने के लिए अच्छा पुस्तकालय समर्थन मिलना मुश्किल है, और यह भी बताना मुश्किल है कि वे कैसे काम करते हैं और आपके सहकर्मियों को क्या लाभ हैं। नमक के एक दाने के साथ इस दृष्टिकोण को लें।


बहुत बढ़िया स्पष्टीकरण! अब मुझे लगता है कि लेंस क्या हैं!
विंसेंट लेक्रबियर

13

मैंने ठीक उसी समस्या पर काम किया है (लेकिन केवल 3 रचना स्तरों के साथ)। मूल विचार क्लोन करना है, फिर संशोधित करना है । अपरिवर्तनीय प्रोग्रामिंग शैली में, क्लोनिंग और संशोधन एक साथ होना होता है, जो कमांड ऑब्जेक्ट बन जाता है

ध्यान दें कि परिवर्तनशील प्रोग्रामिंग शैली में, क्लोनिंग वैसे भी आवश्यक होगा:

  • पूर्ववत / फिर से करने की अनुमति देने के लिए
  • प्रदर्शन प्रणाली को एक साथ "एडिट से पहले" और "एडिट के दौरान" मॉडल को ओवरलैप्ड (भूत लाइनों के रूप में) प्रदर्शित करने की आवश्यकता हो सकती है, ताकि उपयोगकर्ता परिवर्तनों को देख सके।

परिवर्तनशील प्रोग्रामिंग शैली में,

  • मौजूदा संरचना गहरी-क्लोन है
  • क्लोन कॉपी में बदलाव किए गए हैं
  • डिस्प्ले इंजन को भूत-पंक्तियों में पुरानी संरचना और रंग में क्लोन / संशोधित संरचना को प्रस्तुत करने के लिए कहा जाता है।

अपरिवर्तनीय प्रोग्रामिंग शैली में,

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

1
एक अपरिवर्तनीय डेटा संरचना की गहरी प्रतिलिपि क्यों बनाते हैं? आपको केवल संशोधित ऑब्जेक्ट से रूट तक संदर्भों की "रीढ़" को कॉपी करने और तेह मूल संरचना के शेष भागों के संदर्भ को बनाए रखने की आवश्यकता है।
मोनिका

3

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

एक दृष्टिकोण जो विचार करने लायक हो सकता है वह एक सारगर्भित "शायद म्यूटेबल" प्रकार को परिभाषित करने योग्य और गहन-अपरिवर्तनीय व्युत्पन्न प्रकारों के साथ होगा। ऐसे सभी प्रकारों में एक AsImmutableविधि होगी; किसी वस्तु के गहन-अपरिवर्तनीय उदाहरण पर उस विधि को कॉल करने से वह आवृत्ति वापस आ जाएगी। इसे एक उत्परिवर्ती उदाहरण पर कॉल करने से एक गहरी-अपरिवर्तनीय आवृत्ति वापस आ जाएगी, जिसके गुण मूल में उनके समकक्षों के गहरे-अपरिवर्तनीय स्नैपशॉट थे। उत्परिवर्तनीय प्रकारों के साथ अपरिवर्तनीय प्रकार एक AsMutableविधि को स्पोर्ट करते हैं , जो एक उत्परिवर्तनीय उदाहरण का निर्माण करेगा, जिनके गुण मूल से मेल खाते हैं।

एक नेस्टेड ऑब्जेक्ट को एक गहरी-अपरिवर्तनीय वस्तु में बदलने के लिए पहले बाहरी अपरिवर्तनीय वस्तु को एक उत्परिवर्तित के साथ बदलने की आवश्यकता होती है, फिर एक उत्परिवर्ती के साथ बदले जाने वाली चीज़ की जगह संपत्ति आदि की जगह लेती है, लेकिन एक ही पहलू में बार-बार परिवर्तन कर रही है। समग्र ऑब्जेक्ट को किसी भी अतिरिक्त ऑब्जेक्ट को बनाने की आवश्यकता नहीं होगी, जब तक कि AsImmutableएक उत्परिवर्तनीय वस्तु पर कॉल करने का प्रयास नहीं किया गया था (जो कि उत्परिवर्तनीय वस्तुओं को म्यूट कर देगा, लेकिन एक ही डेटा रखने वाली अपरिवर्तनीय वस्तुओं को वापस कर देगा)।

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

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