Let’s travel together.

बॉयलरप्लेट को कम करने के लिए स्केल मैक्रो और क्वैसी-कोट का उपयोग करें

0

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

दुर्भाग्य से, स्काला कोड बॉयलरप्लेट के लिए प्रतिरक्षा नहीं है, और डेवलपर्स ऐसे कोड को रिफलेक्टर और पुन: उपयोग करने का एक तरीका खोजने के लिए संघर्ष कर सकते हैं। उदाहरण के लिए, कुछ पुस्तकालय डेवलपर्स को हस्ताक्षरित वर्ग के प्रत्येक उपवर्ग के लिए एपीआई कॉल करके खुद को दोहराने के लिए मजबूर करते हैं।

हालांकि, यह केवल तब तक सही है जब तक डेवलपर्स यह नहीं सीखते कि संकलन समय पर दोहराया कोड उत्पन्न करने के लिए मैक्रोज़ और अर्ध-उद्धरण का लाभ कैसे उठाया जाए।

मामले का उपयोग करें: एक मूल वर्ग के सभी उपप्रकारों के लिए एक ही हैंडलर पंजीकृत करना

एक माइक्रोसर्विस सिस्टम के विकास के दौरान, मैं एक निश्चित वर्ग से प्राप्त सभी घटनाओं के लिए एक ही हैंडलर पंजीकृत करना चाहता था। हमारे द्वारा उपयोग किए गए ढांचे की बारीकियों से हमें भ्रमित करने से बचने के लिए, ईवेंट हैंडलर्स को पंजीकृत करने के लिए उनके एपीआई की एक सरल परिभाषा है:

trait EventProcessor[Event] {
  def addHandler[E <: Event: ClassTag](
      handler: E => Unit
  ): EventProcessor[Event]

  def process(event: Event)
}

एक घटना प्रोसेसर के लिए नहीं Event प्रकार, हम उपवर्गों के लिए हैंडलर पंजीकृत कर सकते हैं Event साथ addHandler तरीका।

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

स्केल इवेंट्स की पदानुक्रम UserEvent से उतरी।  तीन प्रत्यक्ष वंशज हैं: UserCreated (एक नाम और ई-मेल जो दोनों तार हैं), UserChanged और UserDeleted।  इसके अतिरिक्त, UserChanged के दो बच्चे हैं: NameChanged (एक स्ट्रिंग नाम के साथ) और EmailChanged (स्ट्रिंग स्ट्रिंग के साथ)।
एक पैमाने पर घटना वर्ग पदानुक्रम।

इसी पैमाने की घोषणाएं इस तरह दिखती हैं:

sealed trait UserEvent
final case class UserCreated(name: String, email: String) extends UserEvent
sealed trait UserChanged                                  extends UserEvent
final case class NameChanged(name: String)                extends UserChanged
final case class EmailChanged(email: String)              extends UserChanged
case object UserDeleted                                   extends UserEvent

हम प्रत्येक विशिष्ट घटना वर्ग के लिए एक हैंडलर पंजीकृत कर सकते हैं। क्या होगा अगर हम एक हैंडलर के लिए पंजीकरण करना चाहते हैं सब घटना कक्षाएं? मेरा पहला प्रयास था कि मैं इनके लिए हैंडलर को पंजीकृत करूं UserEvent कक्षा। मुझे उम्मीद थी कि सभी कार्यक्रमों के लिए इसे आमंत्रित किया जाएगा।

val handler   = new EventHandlerImpl[UserEvent]
val processor = EventProcessor[UserEvent].addHandler[UserEvent](handler)

मैंने देखा कि परीक्षण के दौरान हैंडलर को कभी नहीं लगाया गया था। मैंने लागम के कोड को खोदा, जो फ्रेमवर्क मैंने उपयोग किया।

मैंने पाया कि इवेंट प्रोसेसर के कार्यान्वयन ने कुंजी के रूप में पंजीकृत कक्षा के साथ मैपर्स में हैंडलर को संग्रहीत किया है। जब कोई ईवेंट उत्सर्जित होता है, तो वह हैंडलर को कॉल करने के लिए मैप में अपनी कक्षा खोजता है। घटना प्रोसेसर लाइनों द्वारा कार्यान्वित किया जाता है:

type Handler[Event] = (_ <: Event) => Unit

private case class EventProcessorImpl[Event](
    handlers: Map[Class[_ <: Event], List[Handler[Event]]] =
      Map[Class[_ <: Event], List[Handler[Event]]]()
) extends EventProcessor[Event] {

  override def addHandler[E <: Event: ClassTag](
      handler: E => Unit
  ): EventProcessor[Event] = {
    val eventClass =
      implicitly[ClassTag[E]].runtimeClass.asInstanceOf[Class[_ <: Event]]
    val eventHandlers = handler
      .asInstanceOf[Handler[Event]] :: handlers.getOrElse(eventClass, List())
    copy(handlers + (eventClass -> eventHandlers))
  }

  override def process(event: Event): Unit = {
    handlers
      .get(event.getClass)
      .foreach(_.foreach(_.asInstanceOf[Event => Unit].apply(event)))
  }
}

ऊपर, हमने एक हैंडलर को पंजीकृत किया UserEvent वर्ग, लेकिन जब एक व्युत्पन्न घटना की तरह UserCreated उत्सर्जित किया गया था, प्रोसेसर को रजिस्ट्री में अपनी कक्षा नहीं मिलेगी।

इस प्रकार बॉयलरप्लेट कोड शुरू होता है

समाधान प्रत्येक ठोस घटना वर्ग के लिए एक ही हैंडलर को पंजीकृत करना है। हम इसे इस तरह से कर सकते हैं:

val handler = new EventHandlerImpl[UserEvent]  
val processor = EventProcessor[UserEvent]  
  .addHandler[UserCreated](handler)  
  .addHandler[NameChanged](handler)  
  .addHandler[EmailChanged](handler)  
  .addHandler[UserDeleted.type](handler)

अब कोड काम करता है! लेकिन यह दोहराव है।

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

यह निराशाजनक है, जैसा कि UserEvent एक सीलबंद वर्ग है, जिसका अर्थ है कि सभी प्रत्यक्ष उपवर्गों को संकलित समय पर जाना जाता है। क्या होगा अगर हम बॉयलर प्लेट से बचने के लिए जानकारी का उपयोग कर सकते हैं?

बचाने के लिए मैक्रों

आम तौर पर, स्केल फ़ंक्शंस उस मान के आधार पर एक मान दिखाते हैं जो हम उस समय पास करते हैं। आप स्केल मैक्रो को विशेष कार्यों के रूप में सोच सकते हैं जो कुछ कोड उत्पन्न करते हैं संकलन उनके चालान को बदलने का समय।

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

आइए एक नजर डालते हैं macro घोषणा जो किसी दिए गए वर्ग के सभी ज्ञात उपवर्गों के लिए ईवेंट हैंडलर पंजीकरण उत्पन्न करेगी:

def addHandlers[Event](
      processor: EventProcessor[Event],
      handler: Event => Unit
  ): EventProcessor[Event] = macro setEventHandlers_impl[Event]  
  
  
def setEventHandlers_impl[Event: c.WeakTypeTag](c: Context)(
      processor: c.Expr[EventProcessor[Event]],
      handler: c.Expr[Event => Unit]
  ): c.Expr[EventProcessor[Event]] = {

  // implementation here
}

ध्यान दें कि प्रत्येक पैरामीटर के लिए (प्रकार पैरामीटर और रिटर्न प्रकार सहित), कार्यान्वयन विधि में एक पैरामीटर के रूप में संबंधित एएसटी अभिव्यक्ति है। उदाहरण के लिए, c.Expr[EventProcessor[Event]] माचिस EventProcessor[Event]। पैरामीटर c: Context संग्रह संदर्भ लपेटें। हम एकत्रित समय में उपलब्ध सभी जानकारी प्राप्त करने के लिए इसका उपयोग कर सकते हैं।

हमारे मामले में, हम अपने हस्ताक्षरित वर्ग से बच्चों को पुनर्प्राप्त करना चाहते हैं:

import c.universe._  
  
val symbol = weakTypeOf[Event].typeSymbol

def subclasses(symbol: Symbol): List[Symbol] = {  
  val children = symbol.asClass.knownDirectSubclasses.toList  
  symbol :: children.flatMap(subclasses(_))  
}  
  
val children = subclasses(symbol)

पुनरावर्ती कॉल को नोट करें subclasses यह सुनिश्चित करने की विधि कि अप्रत्यक्ष उपवर्गों को भी संसाधित किया जाता है।

अब हमारे पास रजिस्टर करने के लिए इवेंट कक्षाओं की सूची है, हम उस कोड के लिए एएसटी का निर्माण कर सकते हैं जो स्केल मैक्रो उत्पन्न करेगा।

स्केल कोड उत्पन्न करना: एएसटी या क्वाटिक्कॉट्स?

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

आइए सरलता लाभ प्राप्त करने के लिए सरल अभिव्यक्ति को लें a + 2। एएसटी कक्षाओं के साथ इसे उत्पन्न करना इस तरह दिखता है:

val exp = Apply(Select(Ident(TermName("a")), TermName("$plus")), List(Literal(Constant(2))))

हम अधिक संक्षिप्त और पठनीय वाक्य रचना के साथ अर्ध-उद्धरणों के साथ इसे प्राप्त कर सकते हैं:

val exp = q"a + 2"

अपने मैक्रो को सीधा रखने के लिए, हम अर्ध-उद्धरण का उपयोग करते हैं।

आइए एएसटी बनाएं और इसे मैक्रो फ़ंक्शन के परिणाम के रूप में लौटाएं:

val calls = children.foldLeft(q"$processor")((current, ref) =>
  q"$current.addHandler[$ref]($handler)"
)
c.Expr[EventProcessor[Event]](calls)

उपरोक्त कोड एक पैरामीटर के रूप में और प्रत्येक के लिए प्राप्त प्रोसेसर अभिव्यक्ति के साथ शुरू होता है Event उपवर्ग, यह करने के लिए एक कॉल उत्पन्न करता है addHandler पैरामीटर के रूप में उपवर्ग और हैंडलर फ़ंक्शन के साथ विधि।

अब हम इस मैक्रो को कॉल कर सकते हैं UserEvent कक्षा और यह सभी उपवर्गों के लिए हैंडलर को पंजीकृत करने के लिए कोड उत्पन्न करेगा:

val handler = new EventHandlerImpl[UserEvent]  
val processor = EventProcessorMacro.addHandlers(EventProcessor[UserEvent],handler)

कोड क्या उत्पन्न करेगा:

com.example.event.processor.EventProcessor
.apply[com.example.event.handler.UserEvent]()
.addHandler[UserEvent](handler)
.addHandler[UserCreated](handler)
.addHandler[UserChanged](handler)
.addHandler[NameChanged](handler)
.addHandler[EmailChanged](handler)
.addHandler[UserDeleted](handler)

पूरे प्रोजेक्ट का कोड सही तरीके से संकलित है और परीक्षण के मामले बताते हैं कि हैंडलर वास्तव में प्रत्येक उपवर्ग के लिए पंजीकृत है UserEvent। अब हम नए ईवेंट प्रकारों से निपटने के लिए अपने कोड की क्षमता के साथ अधिक आश्वस्त हो सकते हैं।

दोहराव कोड? इसे लिखने के लिए स्केल मैक्रोज़ प्राप्त करें

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

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

Leave A Reply

Your email address will not be published.