क्या एक फ़ंक्शन हस्ताक्षर (FP) की तुलना में एक इंटरफ़ेस (OOP) का सिमेंटिक अनुबंध अधिक जानकारीपूर्ण है?


16

यह कुछ लोगों द्वारा कहा जाता है कि यदि आप ठोस सिद्धांतों को उनके चरम पर ले जाते हैं, तो आप कार्यात्मक प्रोग्रामिंग पर समाप्त होते हैं । मैं इस लेख से सहमत हूं लेकिन मुझे लगता है कि कुछ शब्दार्थ इंटरफ़ेस / ऑब्जेक्ट से फ़ंक्शन / क्लोजर तक के संक्रमण में खो जाते हैं, और मैं जानना चाहता हूं कि कार्यात्मक प्रोग्रामिंग नुकसान को कैसे कम कर सकता है।

लेख से:

इसके अलावा, यदि आप इंटरफ़ेस अलगाव सिद्धांत (ISP) को कठोरता से लागू करते हैं, तो आप समझेंगे कि आपको हैडर इंटरफेस पर रोल इंटरफेसेस का पक्ष लेना चाहिए।

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

public interface IMessageQuery
{
    string Read(int id);
}

अगर मैं एक पर निर्भरता लेता हूं IMessageQuery, तो निहित अनुबंध का एक हिस्सा यह है कि कॉलिंग के Read(id)लिए खोज करेंगे और दिए गए आईडी के साथ एक संदेश वापस करेंगे।

इसके समकक्ष कार्यात्मक हस्ताक्षर पर निर्भरता लेने के लिए इस की तुलना करें, int -> string। बिना किसी अतिरिक्त संकेत के, यह कार्य एक सरल हो सकता है ToString()। यदि आपने I के IMessageQuery.Read(int id)साथ कार्यान्वित किया है ToString()तो आप पर जानबूझकर विध्वंसक होने का आरोप लगा सकते हैं!

तो, कार्यात्मक प्रोग्रामर एक अच्छी तरह से नामित इंटरफ़ेस के शब्दार्थ को संरक्षित करने के लिए क्या कर सकते हैं? क्या यह पारंपरिक है, उदाहरण के लिए, एकल सदस्य के साथ रिकॉर्ड प्रकार बनाएं?

type MessageQuery = {
    Read: int -> string
}

5
एक ओओपी इंटरफ़ेस एफपी टाइपकास्टल की तरह अधिक है , एक फ़ंक्शन नहीं है।
9000

2
आपके पास बहुत अच्छी बात हो सकती है, प्रति इंटरफ़ेस 1 विधि के साथ समाप्त करने के लिए ISP 'सख्ती' को लागू करना अभी बहुत दूर है।
gbjbaanb

3
@ जीबीजैनब वास्तव में मेरे इंटरफेस के अधिकांश हिस्से में कई कार्यान्वयन के साथ केवल एक ही विधि है। जितना अधिक आप SOLID सिद्धांतों को लागू करते हैं, उतना ही आप लाभ देख सकते हैं। लेकिन इस सवाल के लिए यह ऑफ टॉपिक है
AlexFoxGill

1
@jk: ठीक है, हास्केल में, यह टाइप क्लासेस है, ओमेक्एल में या तो आप एक मॉड्यूल या एक फंक्टर का उपयोग करते हैं, क्लोजर में आप एक प्रोटोकॉल का उपयोग करते हैं। किसी भी मामले में, आप आमतौर पर अपने इंटरफ़ेस को एक फ़ंक्शन के अनुरूप नहीं सीमित करते हैं।
9000

2
Without any additional clues... शायद यह है कि प्रलेखन अनुबंध का हिस्सा क्यों है ?
SJuan76

जवाबों:


8

जैसा कि टेलस्टाइन कहते हैं, कार्यों की स्थिर परिभाषाओं की तुलना करना:

public string Read(int id) { /*...*/ }

सेवा

let read (id:int) = //...

आपने वास्तव में OOP से FP तक जाने में कुछ भी नहीं खोया है।

हालाँकि, यह कहानी का केवल एक हिस्सा है, क्योंकि फ़ंक्शन और इंटरफेस केवल उनकी स्थिर परिभाषा में संदर्भित नहीं होते हैं। वे भी पास हो गए । तो मान लें कि हमारा MessageQueryकोड का एक और टुकड़ा पढ़ा गया था, ए MessageProcessor। तो हमारे पास हैं:

public void ProcessMessage(int messageId, IMessageQuery messageReader) { /*...*/ }

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

दूसरी ओर, हमारे कार्यात्मक संस्करण के लिए हमारे पास है:

let read (id:int) (messageReader : int -> string) = // ...

तो हमने क्या रखा है और क्या खोया है? ठीक है, हमारे पास अभी भी पैरामीटर नाम है messageReader, जो संभवतः टाइप नाम (समतुल्य IMessageQuery) को अनावश्यक बनाता है । लेकिन अब हमने पैरामीटर नाम खो दिया हैid अपने फ़ंक्शन में ।


इसके आसपास दो मुख्य तरीके हैं:

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

  • दूसरे, यह कई कार्यात्मक भाषाओं में मुहावरेदार डिजाइन माना जाता है ताकि आदिम को लपेटने के लिए छोटे प्रकार का निर्माण किया जा सके। इस स्थिति में, विपरीत हो रहा है- एक प्रकार के नाम को पैरामीटर नाम के साथ बदलने के बजाय ( IMessageQueryसे messageReader) हम एक प्रकार के नाम के साथ एक पैरामीटर नाम बदल सकते हैं। उदाहरण के लिए, intप्रकार में लपेटा जा सकता है Id:

    type Id = Id of int
    

    अब हमारे readहस्ताक्षर बन गए हैं:

    let read (id:int) (messageReader : Id -> string) = // ...
    

    जो पहले जैसा ही जानकारीपूर्ण था।

    एक साइड नोट के रूप में, यह हमें कुछ संकलक संरक्षण भी प्रदान करता है जो हमारे पास OOP में था। जबकि OOP संस्करण ने सुनिश्चित किया कि हम विशेष IMessageQueryरूप से केवल किसी पुराने int -> stringफ़ंक्शन के बजाय ले गए हैं , यहां हमारे पास एक समान (लेकिन अलग) सुरक्षा है जो हम Id -> stringकेवल किसी पुराने के बजाय ले रहे हैं int -> string


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


1
यह मेरा पसंदीदा उत्तर है - कि एफपी शब्दार्थ प्रकारों के उपयोग को प्रोत्साहित करता है
एलेक्सफॉक्सगिल

10

एफपी करते समय मैं अधिक विशिष्ट अर्थ प्रकार का उपयोग करता हूं।

उदाहरण के लिए, मेरे लिए आपका तरीका कुछ इस तरह का हो जाएगा:

read: MessageId -> Message

यह OO (/ java) -style ThingDoer.doThing()शैली की तुलना में बहुत अधिक संवाद करता है


2
+1। इसके अलावा, आप Haskell जैसी टाइप की गई FP भाषाओं में सिस्टम में कहीं अधिक गुण सांकेतिक शब्दों में बदलना कर सकते हैं। यदि यह एक हैस्केल प्रकार था, तो मुझे पता होगा कि यह किसी भी आईओ को बिल्कुल भी नहीं कर रहा है (और इस तरह कहीं-कहीं संदेश के लिए आईडी की कुछ मेमोरी मैपिंग की संभावना है)। OOP भाषाओं में, मेरे पास वह जानकारी नहीं है - यह फ़ंक्शन किसी डेटाबेस को कॉल करने के भाग के रूप में रिंग कर सकता है।
जैक

1
मैं बिल्कुल स्पष्ट नहीं हूं कि यह जावा शैली से अधिक क्यों संवाद करेगा । क्या read: MessageId -> Messageआपको बताता है कि string MessageReader.GetMessage(int messageId)नहीं करता है
बेन आरोनसन

@BenAaronson के शोर अनुपात का संकेत बेहतर है। यदि यह एक OO उदाहरण विधि है तो मुझे नहीं पता कि यह हुड के नीचे क्या कर रहा है, यह किसी भी प्रकार की निर्भरता हो सकती है जिसे व्यक्त नहीं किया गया है। जैसा कि जैक का उल्लेख है, जब आपके पास मजबूत प्रकार होते हैं, तो आपके पास अधिक आश्वासन होते हैं।
डेनिथ

9

तो, कार्यात्मक प्रोग्रामर एक अच्छी तरह से नामित इंटरफ़ेस के शब्दार्थ को संरक्षित करने के लिए क्या कर सकते हैं?

अच्छी तरह से नामित कार्यों का उपयोग करें।

IMessageQuery::Read: int -> stringबस बन जाता है ReadMessageQuery: int -> stringया कुछ इसी तरह।

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

क्या एक फ़ंक्शन हस्ताक्षर (FP) की तुलना में एक इंटरफ़ेस (OOP) का सिमेंटिक अनुबंध अधिक जानकारीपूर्ण है?

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

एक बार जब आप एक कक्षा में एक से अधिक कार्य / क्षेत्र / संपत्ति प्राप्त करते हैं, तो आप उनके बारे में अधिक जानकारी प्राप्त कर सकते हैं क्योंकि आप उनके संबंध देख सकते हैं। यदि यह स्टैंडअलोन फ़ंक्शंस की तुलना में अधिक जानकारीपूर्ण है तो यह तर्कपूर्ण है कि नेमस्पेस या मॉड्यूल द्वारा आयोजित समान / समान पैरामीटर या स्टैंडअलोन फ़ंक्शंस लेते हैं।

व्यक्तिगत रूप से, मुझे नहीं लगता है कि OO अधिक जटिल उदाहरणों में भी अधिक जानकारीपूर्ण है।


2
पैरामीटर नामों के बारे में क्या?
बेन आरोनसन

@BenAaronson - उनके बारे में क्या? OO और फ़ंक्शनल भाषा दोनों आपको पैरामीटर नाम निर्दिष्ट करते हैं, इसलिए यह एक धक्का है। मैं यहां केवल प्रश्न के अनुरूप होने के लिए हस्ताक्षर हस्ताक्षर का उपयोग कर रहा हूं। एक वास्तविक भाषा में यह कुछ ऐसा दिखेगाReadMessageQuery id = <code to go fetch based on id>
Telastyn

1
वैसे यदि कोई फ़ंक्शन किसी इंटरफ़ेस को पैरामीटर के रूप में लेता है, तो उस इंटरफ़ेस पर एक विधि कहता है, इंटरफ़ेस विधि के लिए पैरामीटर नाम आसानी से उपलब्ध हैं। यदि कोई फ़ंक्शन किसी अन्य फ़ंक्शन को पैरामीटर के रूप में लेता है, तो उस फ़ंक्शन के लिए पैरामीटर नामों को असाइन करना आसान नहीं है
बेन आरोनसन

1
हां, @BenAaronson यह उन सूचनाओं के टुकड़ों में से एक है जो फंक्शन सिग्नेचर में खो जाती है। उदाहरण के लिए let consumer (messageQuery : int -> string) = messageQuery 5, idपैरामीटर सिर्फ एक है int। मुझे लगता है कि एक तर्क यह है कि आपको एक होना चाहिए Id, न कि एक int। वास्तव में जो अपने आप में एक सभ्य जवाब होगा।
एलेक्सफॉक्सगिल

@AlexFoxGill वास्तव में मैं उन बहुत लाइनों के साथ कुछ लिखने की प्रक्रिया में था!
बेन आरोनसन

0

मैं इस बात से असहमत हूं कि किसी एकल फ़ंक्शन में 'सिमेंटिक कॉन्ट्रैक्ट' नहीं हो सकता। इन कानूनों पर विचार करें foldr:

foldr f z nil = z
foldr f z (singleton x) = f x z
foldr f z (xn <> ys) = foldr f (foldr f z ys) xn

किस अर्थ में अर्थ नहीं है, या अनुबंध नहीं है? आपको 'तह' के लिए एक प्रकार को परिभाषित करने की आवश्यकता नहीं है, खासकर क्योंकि foldrउन कानूनों द्वारा विशिष्ट रूप से निर्धारित किया गया है। तुम्हें पता है कि यह क्या करने जा रहा है।

यदि आप किसी प्रकार का कार्य करना चाहते हैं, तो आप एक ही कार्य कर सकते हैं:

-- The first argument `f` must satisfy for all x, y, z
-- > f x x = true
-- > f x y = true and f y x = true implies x = y
-- > f x y = true and f y z = true implies f x z = true
sort :: forall 'a. (a -> a -> bool.t) -> list.t a -> list.t a;

यदि आपको कई बार एक ही अनुबंध की आवश्यकता है, तो आपको केवल उस प्रकार का नाम और कब्जा करना होगा:

-- Type of functions `f` satisfying, for all x, y, z
-- > f x x = true
-- > f x y = true and f y x = true implies x = y
-- > f x y = true and f y z = true implies f x z = true
type comparison.t 'a = a -> a -> bool.t;

टाइप-चेकर आप जिस भी शब्दार्थ को टाइप करने के लिए कहते हैं, उसे लागू नहीं करने वाला है, इसलिए हर कॉन्ट्रैक्ट के लिए एक नया प्रकार बनाना सिर्फ बॉयलरप्लेट है।


1
मुझे नहीं लगता कि आपका पहला बिंदु प्रश्न को संबोधित करता है - यह एक फ़ंक्शन परिभाषा है, हस्ताक्षर नहीं। बेशक एक वर्ग या एक समारोह के कार्यान्वयन को देखकर आप बता सकते हैं कि यह क्या करता है - सवाल पूछता है कि आप अभी भी शब्दार्थ को एक स्तर के अमूर्त (एक इंटरफ़ेस या फ़ंक्शन हस्ताक्षर) पर संरक्षित कर सकते हैं
एलेक्सफॉक्सगिल

@AlexFoxGill - यह एक फ़ंक्शन परिभाषा नहीं है, हालांकि यह विशिष्ट रूप से निर्धारित करता है foldr। संकेत: foldrदो समीकरणों की परिभाषा (क्यों?) है, जबकि ऊपर दिए गए विनिर्देशन में तीन हैं।
जोनाथन

मेरा मतलब है कि तह एक फ़ंक्शन हस्ताक्षर नहीं है। कानून या परिभाषा हस्ताक्षर का हिस्सा नहीं हैं
AlexFoxGill

-1

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

उदाहरण के लिए, मान लें कि एक ग्राहक एक संदेश क्वेरी का कार्यान्वयन चाहता था जो सूची द्वारा समर्थित थी। हास्केल में, कार्यान्वयन इस तरह सरल हो सकता है:

messages = ["Message 0", "Message 1", "Message 2"]
messageQuery = (messages !!)

इसका उपयोग करना newtype Message = Message Stringबहुत कम सीधा होगा, इस कार्यान्वयन को कुछ इस तरह देखना होगा:

messages = map Message ["Message 0", "Message 1", "Message 2"]
messageQuery (Id index) = messages !! index

यह एक बड़ी बात की तरह प्रतीत नहीं हो सकता है, लेकिन आपको या तो उस प्रकार के रूपांतरण को हर जगह आगे और पीछे करना होगा , या अपने कोड में एक सीमा परत स्थापित करना होगा जहां ऊपर सब कुछ है Int -> Stringफिर आप इसे Id -> Messageनीचे की परत में पास करने के लिए परिवर्तित करते हैं। कहें कि मैं अंतर्राष्ट्रीयकरण जोड़ना चाहता था, या इसे सभी कैप्स में प्रारूपित करना चाहता था, या लॉगिंग संदर्भ, या जो कुछ भी जोड़ना चाहता था। उन आपरेशनों के साथ रचना करने के लिए सभी मृत सरल हैं Int -> String, और एक के साथ कष्टप्रद Id -> Message। ऐसा नहीं है कि ऐसे मामले कभी नहीं आते हैं जहां बढ़े हुए प्रकार के प्रतिबंध वांछनीय हैं, लेकिन झुंझलाहट बेहतर व्यापार बंद के लायक थी।

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

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


1
यह बेन / डेनेथ के उत्तरों पर एक टिप्पणी की तरह अधिक लगता है - कि आप शब्दार्थ प्रकारों का उपयोग कर सकते हैं लेकिन असुविधा के कारण आप नहीं हैं? मैंने आपको निराश नहीं किया लेकिन यह इस सवाल का जवाब नहीं है।
एलेक्सफॉक्सगिल

-1 यह कंपोजिबिलिटी या पुन: प्रयोज्यता को नुकसान नहीं पहुंचाएगा। तो Idऔर Messageके लिए सरल रैपर हैं Intऔर String, यह तुच्छ उन दोनों के बीच परिवर्तित करने के लिए।
माइकल शॉ

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

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

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