2012-12-10 25 views
12

मैं स्काला मैक्रो के साथ काम कर रहा है और मैक्रो में निम्न कोड है: एक पुनर्मूल्यांकन खंड में स्कैला मैक्रो में गणना की गई प्रकार का उपयोग कैसे करें?

val fieldMemberType = fieldMember.typeSignatureIn(objectType) match { 
     case NullaryMethodType(tpe) => tpe 
     case _      => doesntCompile(s"$propertyName isn't a field, it must be another thing") 
    } 

    reify{ 
     new TypeBuilder() { 
     type fieldType = fieldMemberType.type 
     } 
    } 

आप देख सकते हैं, मैं एक c.universe.Type fieldMemberType पाने में कामयाब रहे है। यह वस्तु में कुछ फ़ील्ड के प्रकार का प्रतिनिधित्व करता है। एक बार मुझे यह मिलने के बाद, मैं एक नया TypeBuilder ऑब्जेक्ट को पुनः प्राप्त करना चाहता हूं। TypeBuilder एक सार तत्व के साथ एक सार वर्ग है। यह सार पैरामीटर fieldType है। मैं यह fieldType चाहता हूं जो कि मैंने पहले पाया है।

यहां दिखाए गए कोड को चलाने से मुझे fieldMemberType not found लौटाता है। क्या कोई तरीका है कि मैं fieldMemberType को पुनः प्राप्त क्लॉज के अंदर काम करने के लिए प्राप्त कर सकता हूं?

उत्तर

24

समस्या यह है कि आपके द्वारा reify पर जाने वाला कोड अनिवार्य रूप से उस बिंदु पर वर्बैटिम रखा जा रहा है जहां मैक्रो का विस्तार किया जा रहा है, और fieldMemberType का अर्थ कुछ भी नहीं है।

कुछ मामलों में आप splice का उपयोग उस अभिव्यक्ति को छिपाने के लिए कर सकते हैं जो आपके द्वारा संशोधित कोड में मैक्रो-विस्तार समय पर है। उदाहरण के लिए, अगर हम इस विशेषता का एक उदाहरण बनाने के लिए कोशिश कर रहे थे:

reify { new Foo { def i = c.literal(myInt).splice } } 
:

val myInt = 10 

हम निम्नलिखित लिख सकते हैं:

trait Foo { def i: Int } 

और वृहद विस्तार समय में इस चर था

यह यहां काम नहीं करेगा, जिसका मतलब है कि आपको अच्छे छोटे reify के बारे में भूलना होगा और हाथ से एएसटी लिखना होगा। दुर्भाग्यवश, आप पाएंगे कि यह बहुत कुछ होता है।

import scala.reflect.runtime.universe._ 

trait TypeBuilder { type fieldType } 

showRaw(reify(new TypeBuilder { type fieldType = String })) 

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

Ident(TypeBuilder) 
इस के साथ

:

Ident(newTypeName("TypeBuilder")) 

और Flag.FINAL साथ FINAL, और इतने पर। मेरी इच्छा है कि एएसटी प्रकारों के लिए toString विधियों को उनके द्वारा बनाए जाने वाले कोड के लिए बिल्कुल सटीक रूप से मेल किया जाए, लेकिन आपको बहुत जल्दी समझने की आवश्यकता होगी कि आपको क्या बदलना है।

c.Expr(
    Block(
    ClassDef(
     Modifiers(Flag.FINAL), 
     anon, 
     Nil, 
     Template(
     Ident(newTypeName("TypeBuilder")) :: Nil, 
     emptyValDef, 
     List(
      constructor(c), 
      TypeDef(
      Modifiers(), 
      newTypeName("fieldType"), 
      Nil, 
      TypeTree(fieldMemberType) 
     ) 
     ) 
    ) 
    ), 
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil) 
) 
) 

कहाँ anon आप अपनी गुमनाम वर्ग के लिए पहले से बनाए जा चुके प्रकार का नाम है, और constructor एक सुविधा की विधि मैं बात एक छोटे से इस तरह का बनाने के लिए उपयोग है: आप कुछ इस तरह से खत्म हो जाएगा कम घृणास्पद (आप this complete working example के अंत में इसकी परिभाषा पा सकते हैं)।

अब अगर हम this की तरह कुछ में इस अभिव्यक्ति लपेट, हम निम्नलिखित लिख सकते हैं:

scala> TypeMemberExample.builderWithType[String] 
res0: TypeBuilder{type fieldType = String} = [email protected] 

तो यह काम करता है।हमने c.universe.Type (जिसे मैं WeakTypeTag से builderWithType पर टाइप पैरामीटर के यहां से प्राप्त करता हूं, लेकिन यह किसी भी पुराने Type के साथ ठीक उसी तरह काम करेगा) और हमारे TypeBuilder विशेषता के प्रकार के सदस्य को परिभाषित करने के लिए इसका उपयोग किया गया है।

+0

सिर्फ एक प्रश्न, "newTypeName" प्रकार को कैसे पाता है? मेरा मतलब है कि यह कैसे पता चलता है कि यह पैकेज कौन सा है? – mgonto

+0

धन्यवाद! और 'newTypeName' प्रकार का शिकार नहीं करता है-यह सिर्फ उस स्ट्रिंग की प्रतिलिपि बनाता है जिसे आप इसे उस कोड में देते हैं जो मैक्रो उत्पन्न कर रहा है, जिसे तब सामान्य रूप से संकलक द्वारा टाइप-चेक किया जाएगा। यदि आपको पैकेज निर्दिष्ट करने की आवश्यकता है तो आप लिख सकते हैं उदा। 'चुनें (पहचान ("पैकेज"), newTypeName ("MyClass")) '। –

+3

आप जिस कोड में रुचि रखते हैं उसके कोड के स्निपेट बनाने वाले कोड को प्राप्त करने के लिए आप '-Yreify-copypaste' और/या' showRaw' का उपयोग कर सकते हैं। –

6

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

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

तो आपके मामले में, यह निम्नलिखित देगा।

val fieldMemberType: Type = fieldMember.typeSignatureIn(objectType) match { 
     case NullaryMethodType(tpe) => tpe 
     case _      => doesntCompile(s"$propertyName isn't a field, it must be   another thing") 
    } 

    def genRes[T: WeakTypeTag] = reify{ 
    new TypeBuilder() { 
     type fieldType = T 
    } 
    } 

    genRes(c.WeakTypeTag(fieldMemberType)) 
+0

यह एक बहुत साफ दृष्टिकोण है। धन्यवाद – mgonto

+0

+1 (और मुझे यह ध्यान रखना चाहिए था कि यह दृष्टिकोण यहां काम करता है), लेकिन मेरे अनुभव में बहुत से मामले हैं (शायद सबसे अधिक) जहां पेड़ों के साथ काम करना सीधे अपरिहार्य है-विशेष रूप से किसी भी समय आपको संदर्भ (या बनाएं) स्ट्रिंग्स के रूप में उनके नाम से विधियों या कक्षाएं। उदाहरण के लिए, [यहां] (https://github.com/travisbrown/rillit), [यहां] (http://stackoverflow.com/a/13335741/334519), [यहां] (http://stackoverflow.com/ए/13447439/334519) आदि –