क्या प्रकार के मुहावरेदार या खराब डिजाइन के खिलाफ पैटर्न-मिलान है?


18

ऐसा लगता है कि एफ # कोड अक्सर पैटर्न के खिलाफ मेल खाता है। निश्चित रूप से

match opt with 
| Some val -> Something(val) 
| None -> Different()

आम लगता है।

लेकिन एक OOP के नजरिए से, यह रनटाइम प्रकार की जांच के आधार पर नियंत्रण-प्रवाह जैसा एक भयानक रूप दिखता है, जो आम तौर पर विफल हो जाएगा। इसे समाप्त करने के लिए, OOP में आप शायद ओवरलोडिंग का उपयोग करना पसंद करेंगे:

type T = 
    abstract member Route : unit -> unit

type Foo() = 
    interface T with
        member this.Route() = printfn "Go left"

type Bar() = 
    interface T with
        member this.Route() = printfn "Go right"

यह निश्चित रूप से अधिक कोड है। OTOH, यह संरचनात्मक लाभ के लिए मेरे OOP-y मन लगता है:

  • के नए रूप में विस्तार Tआसान है;
  • मुझे मार्ग-चयन नियंत्रण प्रवाह के दोहराव को खोजने के बारे में चिंता करने की आवश्यकता नहीं है; तथा
  • मार्ग का चुनाव इस मायने में अपरिवर्तनीय है कि एक बार मेरे Fooहाथ में होने के बाद, मुझे कभी इसके Bar.Route()कार्यान्वयन की चिंता नहीं करनी चाहिए

क्या उन प्रकारों के खिलाफ पैटर्न-मिलान करने के फायदे हैं जो मैं नहीं देख रहा हूं? क्या इसे मुहावरेदार माना जाता है या क्या यह एक ऐसी क्षमता है जिसका आमतौर पर उपयोग नहीं किया जाता है?


4
एक ओओपी परिप्रेक्ष्य से एक कार्यात्मक भाषा को देखने के लिए कितना अर्थ है? वैसे भी, पैटर्न मिलान की वास्तविक शक्ति नेस्टेड पैटर्न के साथ आती है। बस बाहरी निर्माणकर्ता की जाँच संभव है, लेकिन पूरी कहानी नहीं है।
इंगो

यह - But from an OOP perspective, that looks an awful lot like control-flow based on a runtime type check, which would typically be frowned on.- बहुत हठधर्मी लगता है। कभी-कभी, आप अपने पदानुक्रम से अपने ऑप्स को अलग करना चाहते हैं: हो सकता है कि 1) आप पदानुक्रम b / c में एक ऑप को नहीं जोड़ सकते हैं; 2) जिन वर्गों को आप चाहते हैं, वे आपके पदानुक्रम से मेल नहीं खाते हैं; 3) आप अपने पदानुक्रम में ऑप जोड़ सकते हैं, लेकिन आप अपने पदानुक्रम के API को अव्यवस्था के एक समूह के साथ अव्यवस्थित नहीं करना चाहते हैं, जिसे ज्यादातर ग्राहक उपयोग नहीं करते हैं।

4
बस स्पष्ट करने के लिए, Someऔर Noneप्रकार नहीं हैं। वे दोनों निर्माता हैं जिनके प्रकार हैं forall a. a -> option aऔर forall a. option a(क्षमा करें, निश्चित नहीं है कि टाइप एनोटेशन के लिए वाक्यविन्यास एफ # में है)।

जवाबों:


21

आप सही हैं कि OOP वर्ग पदानुक्रम एफ # में भेदभाव वाली यूनियनों से बहुत निकटता से संबंधित हैं और यह कि पैटर्न मिलान गतिशील प्रकार परीक्षणों से बहुत निकट से संबंधित है। वास्तव में, यह वास्तव में है कि कैसे एफ # संकलन .NET के लिए भेदभाव भेदभाव करता है!

एक्स्टेंसिबिलिटी के संबंध में, समस्या के दो पक्ष हैं:

  • OO आपको नए उप-वर्ग जोड़ने देता है, लेकिन नए (आभासी) कार्यों को जोड़ना कठिन बनाता है
  • FP आपको नए फ़ंक्शन जोड़ने देता है, लेकिन नए यूनियन मामलों को जोड़ना कठिन बनाता है

उस ने कहा, एफ # आपको एक चेतावनी देगा जब आप पैटर्न मिलान में किसी मामले को याद करते हैं, तो नए संघ मामलों को जोड़ना वास्तव में उतना बुरा नहीं है।

रूट चुनने में दोहराव खोजने के बारे में - एफ # आपको एक चेतावनी देगा जब आपके पास एक मैच होगा जो डुप्लिकेट है, जैसे:

match x with
| Some foo -> printfn "first"
| Some foo -> printfn "second" // Warning on this line as it cannot be matched
| None -> printfn "third"

तथ्य यह है कि "मार्ग विकल्प अपरिवर्तनीय है" भी समस्याग्रस्त हो सकता है। उदाहरण के लिए, यदि आप किसी फ़ंक्शन के कार्यान्वयन को मामलों Fooऔर Barमामलों के बीच साझा करना चाहते हैं, लेकिन Zooमामले के लिए कुछ और करते हैं , तो आप पैटर्न मिलान का उपयोग करके आसानी से एन्कोड कर सकते हैं:

match x with
| Foo y | Bar y -> y * 20
| Zoo y -> y * 30

सामान्य तौर पर, एफपी पहले प्रकारों को डिजाइन करने और फिर कार्यों को जोड़ने पर अधिक केंद्रित होता है। तो यह वास्तव में इस तथ्य से लाभान्वित होता है कि आप अपने प्रकार (डोमेन मॉडल) को एक फ़ाइल में कुछ पंक्तियों में फिट कर सकते हैं और फिर आसानी से डोमेन मॉडल पर काम करने वाले कार्यों को जोड़ सकते हैं।

दो दृष्टिकोण - OO और FP काफी पूरक हैं और दोनों के फायदे और नुकसान हैं। मुश्किल बात (OO के नजरिए से आती है) यह है कि एफ # आमतौर पर एफपी शैली को डिफ़ॉल्ट के रूप में उपयोग करता है। लेकिन अगर नए उप-वर्गों को जोड़ने के लिए वास्तव में अधिक आवश्यकता है, तो आप हमेशा इंटरफेस का उपयोग कर सकते हैं। लेकिन अधिकांश प्रणालियों में, आपको समान रूप से प्रकारों और कार्यों को जोड़ने की आवश्यकता होती है, इसलिए पसंद वास्तव में बहुत मायने नहीं रखती है - और एफ # में भेदभाव वाली यूनियनों का उपयोग करना अच्छा है।

मैं अधिक जानकारी के लिए इस महान ब्लॉग श्रृंखला की सिफारिश करूंगा ।


3
यद्यपि आप सही हैं, मैं यह जोड़ना चाहूंगा कि यह OO बनाम FP का इतना अधिक मुद्दा नहीं है क्योंकि यह वस्तुओं बनाम राशि प्रकार का मुद्दा है। उनके साथ OOP का जुनून एक तरफ है, उन वस्तुओं के बारे में कुछ भी नहीं है जो उन्हें अन-फंक्शनल बनाते हैं। और यदि आप पर्याप्त हुप्स के माध्यम से कूदते हैं, तो आप मुख्य प्रकार की ओओपी भाषाओं में योग को लागू कर सकते हैं (हालांकि यह सुंदर नहीं होगा)।
डोभाल

1
"और यदि आप पर्याप्त हुप्स के माध्यम से कूदते हैं, तो आप मुख्य प्रकार की ओओपी भाषाओं में भी योग लागू कर सकते हैं (हालांकि यह सुंदर नहीं होगा)।" -> मुझे लगता है कि आप कुछ इसी तरह समाप्त हो जाएंगे कि कैसे F # योग प्रकार .NET के प्रकार प्रणाली में एन्कोड किए गए हैं :)
Tarmil

8

आपने सही ढंग से देखा है कि पैटर्न मिलान (अनिवार्य रूप से एक सुपरचार्ज स्विच स्टेटमेंट) और गतिशील प्रेषण में समानताएं हैं। वे बहुत ही सुखद परिणाम के साथ कुछ भाषाओं में सह-कलाकार भी हैं। हालांकि, मामूली अंतर हैं।

मैं एक प्रकार को परिभाषित करने के लिए टाइप सिस्टम का उपयोग कर सकता हूं जिसमें केवल एक निश्चित संख्या के उपप्रकार हो सकते हैं:

// pseudocode
data Bool = False | True
data Option a = None | Some item:a
data Tree a = Leaf item:a | Node (left:Tree a) (right:Tree a)

वहाँ होगा कभी नहीं का एक और उप-प्रकार हो Boolया Option, तो उपवर्गीकरण उपयोगी (स्काला जैसे कुछ भाषाओं उपवर्गीकरण कि संभाल कर सकते हैं इस की एक धारणा है होना करने के लिए प्रकट नहीं होता है - एक वर्ग के रूप में वर्तमान संकलन इकाई के "अंतिम" बाहर चिह्नित किया जा सकता है, लेकिन उप-प्रकारों इस संकलन इकाई के अंदर परिभाषित किया जा सकता है)।

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

इसके अलावा, डायनेमिक विधि प्रेषण (जो ओओपी के लिए आवश्यक है) का अर्थ रनटाइम प्रकार की जांच भी है, लेकिन एक अलग तरह का। इसलिए यह काफी अप्रासंगिक है अगर हम इस प्रकार की जाँच एक पैटर्न मैच के माध्यम से या एक विधि कॉल के माध्यम से स्पष्ट रूप से करते हैं।


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

2

F # पैटर्न का मिलान आमतौर पर वर्गों के बजाय एक भेदभाव वाले संघ के साथ किया जाता है (और इस तरह तकनीकी रूप से एक प्रकार की जांच नहीं है)। यह कंपाइलर आपको एक चेतावनी देने की अनुमति देता है जब आप पैटर्न-मैच में मामलों के लिए बेहिसाब होते हैं।

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

एक नया विकल्प जोड़ना तब दिखता है:

  1. अपने भेदभाव वाले संघ में एक नया विकल्प जोड़ें
  2. अधूरे पैटर्न के मैचों पर चेतावनी के सभी को ठीक करें

2

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

if (opt != null)
    opt.Something()
else
    Different()

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

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

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

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


मुझे पता है कि आप यह जानते हैं, और यह संभवत: आपके उत्तर को लिखते समय आपके दिमाग से फिसल गया है, लेकिन ध्यान दें कि Someऔर Noneप्रकार नहीं हैं, इसलिए आप प्रकारों से मेल खाने वाले पैटर्न नहीं हैं। पर आप पैटर्न मैचों की कंस्ट्रक्टर्स का एक ही प्रकार के । यह "Instof" पूछने जैसा नहीं है।
एंड्रेस एफ।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.