राज्य निर्माण योग्य वस्तुओं का निर्माण एक प्रभाव प्रकार से किया जाना चाहिए?


9

स्काला जैसे कार्यात्मक वातावरण का उपयोग करते समय cats-effect, और राज्य वस्तुओं के निर्माण को एक प्रभाव प्रकार के साथ मॉडल किया जाना चाहिए?

// not a value/case class
class Service(s: name)

def withoutEffect(name: String): Service =
  new Service(name)

def withEffect[F: Sync](name: String): F[Service] =
  F.delay {
    new Service(name)
  }

निर्माण गिरने योग्य नहीं है, इसलिए हम एक कमजोर टाइपकास्ट जैसे उपयोग कर सकते हैं Apply

// never throws
def withWeakEffect[F: Applicative](name: String): F[Service] =
  new Service(name).pure[F]

मुझे लगता है कि ये सभी शुद्ध और निर्धारक हैं। हर बार अलग-अलग पारदर्शी रूप से पारदर्शी नहीं होता है क्योंकि परिणामी उदाहरण हर बार अलग होता है। क्या प्रभाव-प्रकार का उपयोग करने का यह अच्छा समय है? या यहां एक अलग कार्यात्मक पैटर्न होगा?


2
हां, उत्परिवर्तित अवस्था का निर्माण एक दुष्प्रभाव है। जैसे, यह एक के अंदर होना चाहिए delayऔर एक एफ [सेवा] वापस करना चाहिए । एक उदाहरण के रूप में, IOstart पर विधि देखें , यह सादे फाइबर के बजाय एक IO [फाइबर [IO?]]] देता है
लुइस मिगुएल मेजिया सुआरेज़

1
इस समस्या के पूर्ण उत्तर के लिए, कृपया इसे और इसे देखें ।
लुइस मिगुएल मेजा सुआरेज़

जवाबों:


3

राज्य निर्माण योग्य वस्तुओं का निर्माण एक प्रभाव प्रकार से किया जाना चाहिए?

यदि आप पहले से ही एक प्रभाव प्रणाली का उपयोग कर रहे हैं, तो इसकी सबसे अधिक संभावना है Ref प्रकार का है जो कि उत्परिवर्तनीय स्थिति को सुरक्षित रूप से घेरता है।

इसलिए मैं कहता हूं: मॉडल स्टेटफुल ऑब्जेक्ट्स के साथRef । निर्माण के बाद से (साथ ही उन तक पहुंच) पहले से ही एक प्रभाव है, यह स्वचालित रूप से सेवा को भी प्रभावशाली बना देगा।

यह बड़े करीने से आपके मूल प्रश्न का पक्ष लेता है।

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


मुझे लगता है कि ये सभी शुद्ध और निर्धारक हैं। हर बार अलग-अलग पारदर्शी रूप से पारदर्शी नहीं होता है क्योंकि परिणामी उदाहरण हर बार अलग होता है। क्या प्रभाव-प्रकार का उपयोग करने का यह अच्छा समय है?

यदि आपके प्रश्न के रूप में rephrased जा सकता है

अतिरिक्त लाभ (संदर्भ के पारदर्शिता के एक "कमजोर टाइपकास्ट" का उपयोग करके सही ढंग से काम करने वाले कार्यान्वयन के ऊपर) और एक प्रभाव-प्रकार (जो पहले से ही राज्य की पहुंच और उत्परिवर्तन के लिए उपयोग में होना है) का उपयोग करने के लिए उचित तर्क देने के लिए पर्याप्त स्थानीय तर्क हैं सृष्टि ?

तब: हाँ, बिल्कुल

यह उपयोगी क्यों है, इसका एक उदाहरण देने के लिए:

निम्नलिखित कार्य ठीक है, भले ही सेवा निर्माण को एक प्रभाव न बनाया गया हो:

val service = makeService(name)
for {
  _ <- service.doX()
  _ <- service.doY()
} yield Ack.Done

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

for {
  _ <- makeService(name).doX()
  _ <- makeService(name).doY()
} yield Ack.Done

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


2

स्टेटफुल सर्विस इस मामले में क्या संदर्भित करती है?

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

या हो सकता है कि आप कह रहे हों कि यह सेवा के अंदर एक परस्पर स्थिति रखता है? जब तक आंतरिक उत्परिवर्तनीय स्थिति उजागर नहीं होती है, तब तक यह ठीक होना चाहिए। आपको बस सेवा के साथ संचार करने के लिए एक शुद्ध (संदर्भित पारदर्शी) विधि प्रदान करने की आवश्यकता है।

मेरे दूसरे बिंदु पर विस्तार करने के लिए:

मान लें कि हम मेमोरी डीबी में निर्माण कर रहे हैं।

class InMemoryDB(private val hashMap: ConcurrentHashMap[String, String]) {
  def getId(s: String): IO[String] = ???
  def setId(s: String): IO[Unit] = ???
}

object InMemoryDB {
  def apply(hashMap: ConcurrentHashMap[String, String]) = new InMemoryDB(hashMap)
}

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

यदि आप Refबिल्लियों-प्रभाव का उपयोग कर रहे हैं , तो मैं सामान्य रूप से क्या करूंगा, flatMapप्रवेश बिंदु पर रेफरी है, इसलिए आपकी कक्षा को प्रभावी होने की आवश्यकता नहीं है।

object Effectful extends IOApp {

  class InMemoryDB(storage: Ref[IO, Map[String, String]]) {
    def getId(s: String): IO[String] = ???
    def setId(s: String): IO[Unit] = ???
  }

  override def run(args: List[String]): IO[ExitCode] = {
    for {
      storage <- Ref.of[IO, Map[String, String]](Map.empty[String, String])
      _ = app(storage)
    } yield ExitCode.Success
  }

  def app(storage: Ref[IO, Map[String, String]]): InMemoryDB = {
    new InMemoryDB(storage)
  }
}

OTOH, यदि आप एक साझा सेवा या एक पुस्तकालय लिख रहे हैं जो एक राज्य वस्तु पर निर्भर है (मान लीजिए कि कई संगामिति आदिमताएं हैं) और आप अपने उपयोगकर्ताओं को इस बात की परवाह नहीं करना चाहते हैं कि प्रारंभिक क्या करें।

फिर, हाँ, इसे एक प्रभाव में लपेटना होगा। आप कुछ का उपयोग कर सकते हैं, Resource[F, MyStatefulService]यह सुनिश्चित करने के लिए कि सब कुछ ठीक से बंद है। या बस F[MyStatefulService]अगर वहाँ बंद करने के लिए कुछ भी नहीं है।


"आपको बस सेवा के साथ संवाद करने के लिए एक शुद्ध विधि प्रदान करने की आवश्यकता है" या शायद इसके ठीक विपरीत: विशुद्ध रूप से आंतरिक राज्य के प्रारंभिक निर्माण के लिए एक प्रभाव होने की आवश्यकता नहीं है, लेकिन उस पारस्परिक स्थिति के साथ संपर्क करने वाली सेवा पर कोई भी संचालन फिर किसी भी तरह से प्रभावी होने की जरूरत है (जैसे दुर्घटनाओं से बचने के लिए val neverRunningThisButStillMessingUpState = Task.pure(service.changeStateThinkingThisIsPure()).repeat(5))
थिलो

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

1
@thilo हाँ, आप सही कह रहे हैं। मेरे कहने का मतलब pureयह है कि इसे संदर्भित रूप से पारदर्शी होना चाहिए। उदाहरण के लिए भविष्य के साथ एक उदाहरण पर विचार करें। val x = Future {... }और def x = Future { ... }एक अलग चीज का मतलब है। (यह आपको तब काट सकता है जब आप अपने कोड को रीक्रिएट कर रहे हों) लेकिन, कैट-इफ़ेक्ट, मोनिक्स या ज़ियो के मामले में ऐसा नहीं है।
atl
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.