इस उदाहरण को कैसे मॉडल करें
इसे रीडर मोनड के साथ कैसे जोड़ा जा सकता है?
मुझे यकीन नहीं है कि यह पाठक के साथ मॉडलिंग की जानी चाहिए, फिर भी यह हो सकता है:
- फ़ंक्शंस के रूप में कक्षाओं को एन्कोडिंग करना जो कोड प्ले रीडर के साथ अच्छे बनाता है
- रीडर के साथ कार्यों को समझने और उसका उपयोग करने के लिए
शुरुआत से ठीक पहले मुझे आपको छोटे नमूना कोड समायोजन के बारे में बताने की ज़रूरत है जो मुझे इस उत्तर के लिए फायदेमंद लगे। पहला बदलाव FindUsers.inactive
विधि के बारे में है। मैंने इसे वापस List[String]
करने दिया ताकि पतों की सूची UserReminder.emailInactive
विधि में उपयोग की जा सके । मैंने विधियों में सरल कार्यान्वयन भी जोड़ा है। अंत में, नमूना रीडर मोनाड के निम्नलिखित हैंड-रोल्ड संस्करण का उपयोग करेगा:
case class Reader[Conf, T](read: Conf => T) { self =>
def map[U](convert: T => U): Reader[Conf, U] =
Reader(self.read andThen convert)
def flatMap[V](toReader: T => Reader[Conf, V]): Reader[Conf, V] =
Reader[Conf, V](conf => toReader(self.read(conf)).read(conf))
def local[BiggerConf](extractFrom: BiggerConf => Conf): Reader[BiggerConf, T] =
Reader[BiggerConf, T](extractFrom andThen self.read)
}
object Reader {
def pure[C, A](a: A): Reader[C, A] =
Reader(_ => a)
implicit def funToReader[Conf, A](read: Conf => A): Reader[Conf, A] =
Reader(read)
}
मॉडलिंग चरण 1. कार्यों के रूप में कक्षाओं को एन्कोडिंग
हो सकता है कि यह वैकल्पिक हो, मुझे यकीन नहीं है, लेकिन बाद में यह समझ के लिए बेहतर बनाता है। ध्यान दें, कि परिणामी फ़ंक्शन करी है। यह पूर्व निर्माता तर्क (ओं) को उनके पहले पैरामीटर (पैरामीटर सूची) के रूप में भी लेता है। उस तरफ
class Foo(dep: Dep) {
def bar(arg: Arg): Res = ???
}
हो जाता है
object Foo {
def bar: Dep => Arg => Res = ???
}
ध्यान रखें की प्रत्येक कि Dep
, Arg
,Res
प्रकार पूरी तरह से मनमाने ढंग से किया जा सकता है: एक टपल, एक समारोह या एक सरल प्रकार।
प्रारंभिक समायोजन के बाद यहां का नमूना कोड, कार्यों में तब्दील हो गया है:
trait Datastore { def runQuery(query: String): List[String] }
trait EmailServer { def sendEmail(to: String, content: String): Unit }
object FindUsers {
def inactive: Datastore => () => List[String] =
dataStore => () => dataStore.runQuery("select inactive")
}
object UserReminder {
def emailInactive(inactive: () => List[String]): EmailServer => () => Unit =
emailServer => () => inactive().foreach(emailServer.sendEmail(_, "We miss you"))
}
object CustomerRelations {
def retainUsers(emailInactive: () => Unit): () => Unit =
() => {
println("emailing inactive users")
emailInactive()
}
}
यहां एक बात गौर करने वाली है कि विशेष कार्य पूरी वस्तुओं पर निर्भर नहीं करते हैं, बल्कि केवल सीधे इस्तेमाल किए जाने वाले भागों पर निर्भर करते हैं। कहां OOP संस्करण में UserReminder.emailInactive()
उदाहरण userFinder.inactive()
यहाँ कॉल यह सिर्फ कॉल करेगा inactive()
- एक कार्य पहले पैरामीटर में इसे पारित कर दिया।
कृपया ध्यान दें, कि कोड प्रश्न से तीन वांछनीय गुणों को प्रदर्शित करता है:
- यह स्पष्ट है कि प्रत्येक कार्यक्षमता को किस प्रकार की निर्भरता चाहिए
- एक कार्यक्षमता की निर्भरता दूसरे से छुपाता है
retainUsers
डेटाटस्टोर निर्भरता के बारे में जानने के लिए विधि की आवश्यकता नहीं होनी चाहिए
स्टेपिंग स्टेप 2. रीडर्स का उपयोग करके फंक्शन्स को कंपोज़ करना और उन्हें रन करना
रीडर मोनाड आपको केवल उन कार्यों की रचना करने देता है जो सभी एक ही प्रकार पर निर्भर करते हैं। यह अक्सर एक मामला नहीं है। हमारे उदाहरण
FindUsers.inactive
में Datastore
और UserReminder.emailInactive
पर निर्भर करता हैEmailServer
। उस समस्या को हल करने के लिए एक नया प्रकार (जिसे अक्सर कॉन्फिगरेशन कहा जाता है) को प्रस्तुत किया जा सकता है, जिसमें सभी निर्भरताएँ होती हैं, फिर फ़ंक्शंस को बदलते हैं ताकि वे सभी इस पर निर्भर रहें और केवल इससे संबंधित डेटा ले सकें। यह स्पष्ट रूप से निर्भरता प्रबंधन के दृष्टिकोण से गलत है क्योंकि इस तरह से आप इन कार्यों को उन प्रकारों पर भी निर्भर करते हैं जिनके बारे में उन्हें पहले से पता नहीं होना चाहिए।
सौभाग्य से यह पता चला है, कि फ़ंक्शन को काम करने का एक तरीका मौजूद है Config
भले ही यह एक पैरामीटर के रूप में इसके कुछ हिस्से को स्वीकार करता है। यह एक विधि है जिसे local
रीडर में परिभाषित किया गया है। इसे संबंधित भाग को निकालने के लिए एक तरीका प्रदान करना होगा Config
।
उदाहरण के लिए हाथ पर लागू यह ज्ञान इस तरह दिखेगा:
object Main extends App {
case class Config(dataStore: Datastore, emailServer: EmailServer)
val config = Config(
new Datastore { def runQuery(query: String) = List("john.doe@fizzbuzz.com") },
new EmailServer { def sendEmail(to: String, content: String) = println(s"sending [$content] to $to") }
)
import Reader._
val reader = for {
getAddresses <- FindUsers.inactive.local[Config](_.dataStore)
emailInactive <- UserReminder.emailInactive(getAddresses).local[Config](_.emailServer)
retainUsers <- pure(CustomerRelations.retainUsers(emailInactive))
} yield retainUsers
reader.read(config)()
}
कंस्ट्रक्टर मापदंडों का उपयोग करने पर लाभ
इस तरह के "व्यावसायिक अनुप्रयोग" के लिए पाठक मोनाद का उपयोग करने वाले पहलुओं में केवल निर्माण मापदंडों का उपयोग करने से बेहतर क्या होगा?
मुझे उम्मीद है कि इस उत्तर को तैयार करके मैंने अपने लिए खुद को आंकना आसान बना दिया कि किन पहलुओं में यह सादे रचनाकारों को हरा देगा। फिर भी अगर मैं इनकी गणना करता, तो यहां मेरी सूची है। डिस्क्लेमर: मेरे पास OOP बैकग्राउंड है और मैं रीडर और क्लेस्ली की पूरी तरह से सराहना नहीं कर सकता क्योंकि मैं उनका इस्तेमाल नहीं करता।
- एकरूपता - कोई भी मैटर, संक्षिप्तता के लिए कितना छोटा / लंबा है, यह सिर्फ एक पाठक है और आप इसे आसानी से किसी अन्य उदाहरण के साथ रचना कर सकते हैं, शायद केवल एक और कॉन्फिगर प्रकार का परिचय दे रहे हैं और
local
इसके ऊपर कुछ कॉल छिड़क रहे हैं। यह बिंदु IMO है, बल्कि स्वाद का विषय है, क्योंकि जब आप कंस्ट्रक्टरों का उपयोग करते हैं, तो कोई भी आपको अपनी पसंद की चीज़ों की रचना करने से रोकता है, जब तक कि कोई व्यक्ति कुछ बेवकूफी न करे, जैसे कि कंस्ट्रक्टर में काम करना जो ओओपी में एक बुरा अभ्यास माना जाता है।
- रीडर, एक इकाई है, इसलिए यह है कि से संबंधित सभी लाभ हो जाता है -
sequence
, traverse
तरीकों मुक्त करने के लिए लागू किया।
- कुछ मामलों में आपको पाठक को केवल एक बार निर्माण करना और इसे विस्तृत रूप से कन्फ़िगर्स के लिए उपयोग करना बेहतर लग सकता है। कंस्ट्रक्टर्स के साथ कोई भी आपको ऐसा करने से रोकता है, आपको बस आने वाले हर कॉन्फिग के लिए पूरी ऑब्जेक्ट ग्राफ नए सिरे से बनाने की जरूरत है। जबकि मुझे इससे कोई समस्या नहीं है (मैं आवेदन करने के लिए हर अनुरोध पर भी ऐसा करना पसंद करता हूं), यह कई लोगों के लिए एक स्पष्ट विचार नहीं है कि जिन कारणों से मैं केवल अटकलें लगा सकता हूं।
- रीडर आपको फ़ंक्शंस का अधिक उपयोग करने की ओर धकेलता है, जो मुख्यतः FP शैली में लिखे गए एप्लिकेशन के साथ बेहतर तरीके से निभाएगा।
- पाठक चिंताओं को अलग करता है; आप सब कुछ के साथ बातचीत कर सकते हैं, निर्भरता प्रदान किए बिना तर्क को परिभाषित कर सकते हैं। दरअसल बाद में, अलग से आपूर्ति। (इस बिंदु के लिए धन्यवाद केन स्क्रैम्बलर)। यह अक्सर पाठक का लाभ सुना जाता है, फिर भी सादे निर्माणकर्ताओं के साथ भी संभव है।
मैं यह भी बताना चाहूंगा कि मुझे रीडर में क्या पसंद नहीं है।
- विपणन। कभी-कभी मुझे आभास हो जाता है, कि रीडर को सभी तरह की निर्भरता के लिए विपणन किया जाता है, बिना भेद के अगर वह सत्र कुकी या डेटाबेस है। मेरे लिए व्यावहारिक रूप से निरंतर वस्तुओं के लिए रीडर का उपयोग करने में बहुत कम समझ है, जैसे ईमेल सर्वर या इस उदाहरण से रिपॉजिटरी। ऐसी निर्भरता के लिए मुझे सादे निर्माणकर्ता और / या आंशिक रूप से लागू किए गए फ़ंक्शन बेहतर तरीके से मिलते हैं। अनिवार्य रूप से रीडर आपको लचीलापन देता है ताकि आप हर कॉल पर अपनी निर्भरता को निर्दिष्ट कर सकें, लेकिन अगर आपको वास्तव में इसकी आवश्यकता नहीं है, तो आप केवल इसके कर का भुगतान करते हैं।
- अस्पष्ट भारीपन - पाठक को बिना प्रभाव के उपयोग करने से उदाहरण को पढ़ने में मुश्किल होगी। दूसरी ओर, जब आप नपुंसकता का उपयोग करके शोर वाले हिस्सों को छिपाते हैं और कुछ त्रुटि करते हैं, तो संकलक कभी-कभी आपको संदेशों को समझने में मुश्किल देगा।
- के साथ समारोह
pure
, local
और खुद के विन्यास कक्षाएं बनाने / उस के लिए tuples का उपयोग कर। रीडर आपको कुछ कोड जोड़ने के लिए मजबूर करता है जो समस्या डोमेन के बारे में नहीं है, इसलिए कोड में कुछ शोर का परिचय दे रहा है। दूसरी ओर, एक एप्लिकेशन जो कंस्ट्रक्टर का उपयोग करता है, अक्सर फ़ैक्टरी पैटर्न का उपयोग करता है, जो समस्या डोमेन के बाहर से भी है, इसलिए यह कमजोरी इतनी गंभीर नहीं है।
यदि मैं अपनी कक्षाओं को कार्यों के साथ वस्तुओं में बदलना नहीं चाहता तो क्या होगा?
तुम्हें चाहिए। आप तकनीकी रूप से इससे बच सकते हैं , लेकिन अगर मैं FindUsers
कक्षा को ऑब्जेक्ट में परिवर्तित नहीं करता तो क्या होगा । समझ के लिए संबंधित लाइन की तरह दिखेगा:
getAddresses <- ((ds: Datastore) => new FindUsers(ds).inactive _).local[Config](_.dataStore)
जो पठनीय नहीं है, वह क्या है? मुद्दा यह है कि रीडर फ़ंक्शंस पर काम करता है, इसलिए यदि आपके पास पहले से नहीं है, तो आपको उन्हें इनलाइन बनाने की आवश्यकता है, जो अक्सर उतना सुंदर नहीं होता है।