2017-08-11 6 views
5

मेरे पास एक केस क्लास में लगभग 20 फ़ील्ड शामिल हैं, जिनमें से सभी आदिम प्रकार हैं।स्कैला विकल्प पार्सर का उपयोग कर जेनेरिक केस क्लास फ़ील्ड को कैसे पार्स करें?

case class A(f1: String, f2: Int .....) 

और मुझे इन फ़ील्ड को कमांड लाइन (दुर्भाग्य से) से पार्स करना होगा। मैं यह कर सकते हैं, लेकिन मैं वास्तव में लिखने के लिए इस 20 बार

opt[String]("f1") required() valueName "<f1>" action { (x, c) => 
    c.copy(f1 = x) 
    } text "f1 is required" 
//...repeat 20 times 

मैं प्रतिबिंब के माध्यम से क्षेत्र का नाम और दायर प्रकार प्राप्त कर सकते हैं नहीं करना चाहती है, लेकिन मुझे नहीं पता कि कैसे एक के भीतर इस कॉल करने के लिए उन जानकारी अटक करने के लिए है लूप

मैं इसे आकारहीन से जोड़ सकता हूं लेकिन मैं अभी भी उससे परिचित नहीं हूं और क्या यह बिना किसी आकार के किया जा सकता है?

==

स्केला विकल्प पार्सर => scopt

+0

मुझे लगता है कि "स्कैला विकल्प पार्सर" से आपका मतलब है [स्कॉप्ट] (https://github.com/scopt/scopt)? – thibr

+0

यदि आप ऐसा करना चाहते हैं तो आप खुद को बेकार बना देंगे। आप 'LabelledGeneric' जैसे कुछ ढूंढ रहे हैं। – Alec

+0

@ एलेक, पहियों को फिर से कार्यान्वित करना पूरी तरह से स्वीकार्य है, जब तक मुझे केवल कोड का एक छोटा टुकड़ा लिखना पड़ेगा। – zinking

उत्तर

3

मैं सिर्फ देखा है कि आप निराकार की तरह कोई पुस्तकालयों चाहता था। यदि यह कोई सांत्वना है तो यह एक लाइब्रेरी है जो स्काला को अंततः मैक्रोज़ को प्रतिबिंबित करेगी, इसलिए यह चावल को फिर से शुरू किए बिना आपको शुद्ध स्केल के करीब के करीब है।

मुझे लगता है कि मेरे पास ऐसा कुछ हो सकता है जो इससे मदद कर सके। यह एक तरह का भारी समाधान है, लेकिन मुझे लगता है कि यह वही करेगा जो आप पूछ रहे हैं।

यह स्थैतिक एनोटेशन बनाने के लिए शानदार स्कैलेटाटा (http://www.scalameta.org) लाइब्रेरी का उपयोग करता है। आप अपनी केस क्लास को एनोटेट करेंगे और यह इनलाइन मैक्रो आपके कमांड लाइन तर्कों के लिए उपयुक्त स्कोप पार्सर उत्पन्न करेगा।

आपके build.sbt को मैक्रो पैराडाइज प्लगइन के साथ-साथ स्केलेमेटा लाइब्रेरी की आवश्यकता होगी। आप इन्हें अपने प्रोजेक्ट में जोड़ सकते हैं।

addCompilerPlugin("org.scalameta" % "paradise" % paradise cross CrossVersion.full) 
libraryDependencies ++= Seq(
    "org.scalameta" %% "scalameta" % meta % Provided, 
) 

एक बार जब आप अपने निर्माण करने के लिए उन deps जोड़ लिया है क्या आप मैक्रो के लिए एक अलग प्रोजेक्ट बनाने के लिए होगा।

एक पूरा एसबीटी परियोजना परिभाषा की तरह

lazy val macros = project 
    .in(file("macros")) 
    .settings(
    addCompilerPlugin("org.scalameta" % "paradise" % paradise cross CrossVersion.full), 
    libraryDependencies ++= Seq(
     "org.scalameta" %% "scalameta" % "1.8.0" % Provided, 
    ) 
    ) 

मॉड्यूल में ही "मैक्रो" नाम दिया गया है, तो विचार करेंगे, तो एक वर्ग बना सकते हैं और यहां स्थिर एनोटेशन है।

import scala.annotation.{StaticAnnotation, compileTimeOnly} 
import scala.meta._ 

@compileTimeOnly("@Opts not expanded") 
class Opts extends StaticAnnotation { 
    inline def apply(defn: Any): Any = meta { 
    defn match { 
     case q"..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) extends $template" => 
     val opttpe = Type.Name(tname.value) 
     val optName = Lit.String(tname.value) 
     val opts = paramss.flatten.map { 
      case param"..${_} $name: ${tpeopt: Option[Type]} = $expropt" => 
      val tpe = Type.Name(tpeopt.get.toString()) 
      val litName = Lit.String(name.toString()) 
      val errMsg = Lit.String(s"${litName.value} is required.") 
      val tname = Term.Name(name.toString()) 
      val targ = Term.Arg.Named(tname, q"x") 
      q""" 
       opt[$tpe]($litName) 
        .required() 
        .action((x, c) => c.copy($targ)) 
        .text($errMsg) 
      """ 
     } 
     val stats = template.stats.getOrElse(Nil) :+ q"def options: OptionParser[$opttpe] = new OptionParser[$opttpe]($optName){ ..$opts }" 
     q"""..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) { 
      import scopt._ 
      ..$stats 
     }""" 
    } 
    } 
} 

इसके बाद आप अपना मुख्य मॉड्यूल अपने मैक्रोज़ मॉड्यूल पर निर्भर करेंगे। तो फिर तुम तो जैसे अपने मामले कक्षाएं टिप्पणी कर सकते हैं ...

@Opts 
case class Options(name: String, job: String, age: Int, netWorth: Double, job_title: String) 

यह तो संकलन समय पर scopt परिभाषाओं में शामिल करने के लिए अपने मामले वर्ग का विस्तार होगा। यहां एक जेनरेट क्लास ऊपर से जैसा दिखता है।

case class Options(name: String, job: String, age: Int, netWorth: Double, job_title: String) { 
    import scopt._ 

    def options: OptionParser[Options] = new OptionParser[Options]("Options") { 
    opt[String]("name").required().action((x, c) => c.copy(name = x)).text("name is required.") 
    opt[String]("job").required().action((x, c) => c.copy(job = x)).text("job is required.") 
    opt[Int]("age").required().action((x, c) => c.copy(age = x)).text("age is required.") 
    opt[Double]("netWorth").required().action((x, c) => c.copy(netWorth = x)).text("netWorth is required.") 
    opt[String]("job_title").required().action((x, c) => c.copy(job_title = x)).text("job_title is required.") 
    } 
} 

यह आपको बॉयलर प्लेट की बचत हो जाना चाहिए, और इनलाइन मैक्रो की अधिक ज्ञान के साथ किसी के लिए कृपया मुझे बताओ करने के लिए मैं इस बेहतर कैसे लिख सकता है के लिए स्वतंत्र महसूस, के बाद से मैं इस पर एक विशेषज्ञ नहीं हूँ।

आप इस बारे में उचित ट्यूटोरियल और दस्तावेज़ीकरण http://scalameta.org/tutorial/#Macroannotations पर देख सकते हैं मुझे इस दृष्टिकोण के बारे में आपके किसी भी प्रश्न का उत्तर देने में भी खुशी है!

+0

धन्यवाद, कारण मैं आकारहीन नहीं चाहता था संकलन प्लगइन्स की इसकी आवश्यकता है क्योंकि मैं स्कैला 2.10 पर हूं। उस तरफ रखो, मैंने सोचा कि केवल कुछ सामान्य और जटिल कार्य घोषणाओं को हल करना चाहिए, यह जटिल क्यों होना चाहिए? – zinking

+0

मैं समझता हूं। मुझे लगता है कि आप स्कैला 2.10 का उपयोग कर रहे हैं? यदि आप आकारहीन नहीं हैं तो किसी भी कंपाइलर प्लगइन्स को कार्य करने की आवश्यकता नहीं है। मुझे नहीं लगता कि यह एक विशेष रूप से कठिन समस्या है, यह सिर्फ इतना है कि इस समस्या के लिए आपको संकलन समय पर नया कोड उत्पन्न करने की आवश्यकता है, इसलिए मैक्रो। –

+0

आपने वास्तव में कैसे निष्कर्ष निकाला कि इसके लिए कोड जनरेशन की आवश्यकता है? मैंने सोचा कि यह सिर्फ रनटाइम पर हासिल किया जा सकता है। – zinking

2

यहां केवल एक रनटाइम प्रतिबिंब के साथ लागू संस्करण है।

libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value 

कोड::

import scala.collection.mutable 
import scala.reflect.runtime.universe._ 

def genericParser[T: TypeTag](programName: String): OptionParser[T] = new OptionParser[T](programName) { 
    val StringTpe: Type = typeOf[String] 

    val fields: List[MethodSymbol] = typeOf[T].decls.sorted.collect { 
    case m: MethodSymbol if m.isCaseAccessor ⇒ m 
    } 

    val values = mutable.Map.empty[TermName, Any] 

    /** 
    * Returns an instance of a [[scopt.Read]] corresponding to the provided type 
    */ 
    def typeToRead(tpe: Type): Read[Any] = (tpe match { 
    case definitions.IntTpe ⇒ implicitly[Read[Int]] 
    case StringTpe   ⇒ implicitly[Read[String]] 
     // Add more types if necessary... 
    }) map identity[Any] 

    for (f ← fields) { 
    // kind of dynamic implicit resolution 
    implicit val read: Read[Any] = typeToRead(f.returnType) 
    opt[Any](f.name.toString) required() valueName s"<${f.name}>" foreach { value ⇒ 
     values(f.name) = value 
    } text s"${f.name} is required" 
    } 

    override def parse(args: Seq[String], init: T): Option[T] = { 
    super.parse(args, init) map { _ ⇒ 
     val classMirror = typeTag[T].mirror.reflectClass(typeOf[T].typeSymbol.asClass) 
     val constructor = typeOf[T].decl(termNames.CONSTRUCTOR).asMethod 
     val constructorMirror = classMirror.reflectConstructor(constructor) 
     val constructorArgs = constructor.paramLists.flatten.map(symbol ⇒ values(symbol.asTerm.name)) 

     constructorMirror(constructorArgs: _*).asInstanceOf[T] 
    } 
    } 
} 

उदाहरण उपयोग:

case class A(f1: String, f2: Int) 

println(genericParser[A]("main").parse(args, A("", -1))) 

कुछ चीजों को हालांकि यह एक मैक्रो आधारित समाधान की तुलना में कम सुरुचिपूर्ण है, यह केवल स्केला-reflect.jar की आवश्यकता है ध्यान में रखें:

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