2015-09-22 15 views
5

यह एक आम जगह है जो लेक्सिकल गुंजाइश तक पहुंच को संकलित समय (या एक स्थिर विश्लेषक द्वारा, क्योंकि मेरा उदाहरण पायथन में है) पर स्रोत कोड में स्थान पर आधारित किया जा सकता है।क्या लेक्सिकल स्कोप में गतिशील पहलू है?

यहां एक बहुत ही सरल उदाहरण है जहां एक समारोह में a के लिए अलग-अलग मानों के साथ दो बंद होते हैं।

def elvis(a): 
    def f(s): 
    return a + ' for the ' + s 
    return f 

f1 = elvis('one') 
f2 = elvis('two') 
print f1('money'), f2('show') 

मैं विचार जब हम समारोह f, जब हम a देखते हैं, यह f में निर्धारित नहीं है के लिए कोड पढ़ रहे हैं, तो हम संलग्नित कार्य करने के लिए पॉप अप और वहाँ एक पाते हैं कि साथ कोई समस्या नहीं है , और f में a यही है। स्रोत कोड में स्थान मुझे यह बताने के लिए पर्याप्त है कि f को a के लिए एक संलग्न दायरे से मूल्य मिलता है।

लेकिन जैसा कि here वर्णित है, जब कोई फ़ंक्शन कहा जाता है, तो इसका स्थानीय फ्रेम इसके मूल वातावरण को बढ़ाता है। तो रनटाइम पर पर्यावरण लुकअप करना कोई समस्या नहीं है। लेकिन मुझे यह सुनिश्चित नहीं है कि एक स्थिर विश्लेषक हमेशा काम कर सकता है जो कोड को चलाने से पहले संकलन समय पर बंद किया जाता है। उपर्युक्त उदाहरण में यह स्पष्ट है कि elvis में दो बंद हैं और इन्हें ट्रैक रखना आसान है, लेकिन अन्य मामले इतना आसान नहीं होंगे। सहजता से मैं घबरा गया हूं कि स्थैतिक विश्लेषण का प्रयास सामान्य रूप से एक रोकथाम की समस्या में चला सकता है।

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

या क्या उत्तर प्रोग्रामिंग भाषा पर निर्भर करता है - इस मामले में लेक्सिकल स्कोपिंग एक अवधारणा जितनी मजबूत नहीं है जैसा मैंने सोचा था?

[संपादित करें @comments:

मेरी उदाहरण के मामले में मैं अपने प्रश्न को फिर से कर सकते हैं: मैं तरह का दावा पढ़ें "शाब्दिक संकल्प संकलन समय पर निर्धारित किया जा सकता," अभी तक सोचा कि f1 में a के मूल्य के संदर्भ और f2 को स्थिर रूप से/संकलन समय (सामान्य रूप से) पर काम किया जा सकता है।

समाधान है, लेक्सिकल स्कॉपिंग इतना दावा नहीं करता है। एल.एस. हमें संकलन समय पर बता सकते हैं, यह है कि कुछa बुलाया परिभाषित किया जाएगा जब भी मैं f में हूँ (और यह स्पष्ट रूप से स्थिर बाहर काम किया जा सकता है, इस शाब्दिक गुंजाइश की परिभाषा है), लेकिन निर्धारण मूल्य यह वास्तव में ले जाता है (या, कौन सा बंद सक्रिय है) 1) एलएस से परे है अवधारणा, 2) रनटाइम (स्थिर रूप से नहीं) पर किया जाता है, इसलिए एक अर्थ में गतिशील है, फिर भी 3) गतिशील स्कॉइंग से अलग नियम का उपयोग करता है।

@ पैट्रिक मैपिन को उद्धृत करने के लिए टेकवे संदेश, "कुछ गतिशील काम अभी भी किया जाना है।" ]

+0

इसका क्या अर्थ हो सकता है? प्रोग्रामिंग भाषाओं को निर्धारक होना चाहिए, अन्यथा हम उनका उपयोग नहीं करेंगे। चीजें काफी कठिन हैं क्योंकि यह है। – wallyk

+0

मुझे यकीन नहीं है कि मैं सवाल समझता हूं। क्या आप पूछ रहे हैं कि 'f1' और' f2' के मान स्थिर विश्लेषण का उपयोग करके निर्धारित किए जा सकते हैं? – Barmar

+0

परिभाषा के अनुसार, व्याख्यात्मक दायरा पूरी तरह से व्याख्यात्मक है। आप बस संलग्न लेक्सिकल फ़ंक्शन, और इसके संलग्न कार्य को देखते हैं, और इसी तरह। – Barmar

उत्तर

5

क्लोजर कई तरीकों से कार्यान्वित किया जा सकता है। उनमें से एक वास्तव में वातावरण पर कब्जा करने की है ... दूसरे शब्दों में उदाहरण पर विचार

def foo(x): 
    y = 1 
    z = 2 
    def bar(a): 
     return (x, y, a) 
    return bar 

env-कब्जा समाधान प्रकार है:

  1. foo दर्ज किया गया है और एक स्थानीय फ्रेम बनाया गया है कि x शामिल , y, z, bar नाम। नाम x को बंद करने के लिए पैरामीटर, नाम y और z 1 और 2 के लिए, नाम bar के लिए बाध्य है
  2. बंद bar करने के लिए सौंपा वास्तव में पूरी माता पिता फ्रेम कब्जा इसलिए जब यह उस में नाम a देखने कर सकते हैं कहा जाता है अपने स्वयं के स्थानीय फ्रेम और कब्जे वाले पैरेंट फ्रेम में x और y देख सकते हैं।
इस दृष्टिकोण के साथ

चर z, बंद के रूप में जीवित रहता है जब तक जीवित रहेगा, भले ही वह बंद द्वारा संदर्भित नहीं कर रहा है (कि नहीं दृष्टिकोण अजगर द्वारा प्रयोग किया जाता है)।

एक अन्य विकल्प, थोड़ा और अधिक जटिल बजाय की तरह काम करता है लागू करने के लिए: संकलन समय कोड विश्लेषण किया जाता है और बंद करने के लिए सौंपा bar वर्तमान क्षेत्र से नाम पर कब्जा करने x और y की खोज की है पर

  1. इन दो चर इसलिए "सेल" के रूप में वर्गीकृत किया जाता है और वे अलग-अलग स्थानीय फ्रेम से
  2. बंद दुकानों इनके पते आवंटित किए जाते हैं और उन्हें करने के लिए प्रत्येक पहुँच एक डबल अविवेक की आवश्यकता है (एक सेल जहां के लिए सूचक है मूल्य वास्तव में संग्रहीत किया जाता है)

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

यह पाइथन करता है ... मूल रूप से एक बंद होने पर संकलित समय पर (या तो नामित फ़ंक्शन या lambda) एक उप-संकलन किया जाता है। संकलन के दौरान जब एक लुकअप होता है जो एक पैरेंट फ़ंक्शन को हल करता है तो चर को सेल के रूप में चिह्नित किया जाता है।

एक छोटी परेशानी यह है कि जब एक पैरामीटर कैप्चर किया जाता है (foo उदाहरण में) एक सेल में पास किए गए मान को बदलने के लिए प्रस्तावना में एक अतिरिक्त प्रतिलिपि भी करने की आवश्यकता होती है। यह पायथन में बाइटकोड में दिखाई नहीं दे रहा है लेकिन कॉल मशीनरी द्वारा सीधे किया जाता है।

एक और परेशानी यह है कि एक कब्जे वाले चर के लिए प्रत्येक पहुंच को मूल संदर्भ में भी एक डबल संकेत की आवश्यकता होती है।

लाभ यह है कि बंद ही वास्तव में संदर्भित चर पर कब्जा है और वे पर कब्जा नहीं है जब किसी भी उत्पन्न कोड एक नियमित रूप से समारोह के रूप में के रूप में कुशल है।

यह कैसे पायथन में काम करता है यह देखने के लिए आप उत्पन्न बाईटकोड का निरीक्षण करने के dis मॉड्यूल का उपयोग कर सकते हैं: एक STORE_DEREF (आपरेशन है कि एक के लिए लिखते हैं साथ y में

>>> dis.dis(foo) 
    2   0 LOAD_CONST    1 (1) 
       3 STORE_DEREF    1 (y) 

    3   6 LOAD_CONST    2 (2) 
       9 STORE_FAST    1 (z) 

    4   12 LOAD_CLOSURE    0 (x) 
      15 LOAD_CLOSURE    1 (y) 
      18 BUILD_TUPLE    2 
      21 LOAD_CONST    3 (<code object bar at 0x7f6ff6582270, file "<stdin>", line 4>) 
      24 LOAD_CONST    4 ('foo.<locals>.bar') 
      27 MAKE_CLOSURE    0 
      30 STORE_FAST    2 (bar) 

    6   33 LOAD_FAST    2 (bar) 
      36 RETURN_VALUE 
>>> 

के रूप में आप उत्पन्न कोड भंडार 1 देख सकते हैं सेल, इस प्रकार एक डबल अविवेक का प्रयोग करके) और बदले भंडार z में 2 का उपयोग कर एक STORE_FAST (z कब्जा नहीं कर रहा था और सिर्फ वर्तमान फ्रेम में एक स्थानीय है)। जब foo का कोड निष्पादन शुरू होता है x कॉल मशीनरी द्वारा पहले ही सेल में लपेटा गया है।

bar सिर्फ एक स्थानीय चर रहा है, इसलिए STORE_FAST इसे करने के लिए लिखने के लिए है, लेकिन बंद x और y व्यक्तिगत रूप से (वे MAKE_CLOSURE opcode लागू करने से पहले एक टपल में डाल रहे हैं) कॉपी किया जा करने की जरूरत है निर्माण करने के लिए प्रयोग किया जाता है।

बंद खुद के लिए कोड के साथ दिख रहा है:

>>> dis.dis(foo(12)) 
    5   0 LOAD_DEREF    0 (x) 
       3 LOAD_DEREF    1 (y) 
       6 LOAD_FAST    0 (a) 
       9 BUILD_TUPLE    3 
      12 RETURN_VALUE 

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

ऐसे कंपाइलर्स जो एसबीसीएल (सामान्य लिस्प जनरेटिंग देशी कोड के लिए एक अनुकूलन कंपाइलर) की तरह अधिक परिष्कृत हैं, यह भी पता लगाने के लिए कि "बंदरगाह वास्तव में संलग्न कार्य को जीवित कर सकता है या नहीं, यह पता लगाने के लिए" बचने का विश्लेषण "भी करता है। जब यह (यानी अगर bar केवल foo के अंदर इस्तेमाल और नहीं संग्रहीत या दिया जाता है) नहीं होता है कोशिकाओं, स्टैक में के बजाय ढेर पर आवंटित किया जा सकता है क्रम "consing" की राशि को कम करने (ढेर पर वस्तुओं के आवंटन जिसके लिए कचरा संग्रह पुनः प्राप्त करने की आवश्यकता है)।

यह भेद साहित्य में है "डाउनवर्ड/ऊपर की ओर मज़ेदार" के रूप में जाना जाता है; अर्थात कब्जा कर लिया चर (बंद में या बंद करने के अंदर बनाया गहरी बंद में अर्थात) या भी ऊपरी स्तरों में निचले स्तरों में ही दिखाई देते हैं अगर (अर्थात, अगर मेरी फोन करने वाले मेरी कब्जा कर लिया स्थानीय लोगों उपयोग करने में सक्षम हो जाएगा)।

ऊपर की ओर funarg समस्या एक कचरा कलेक्टर की जरूरत है हल करने के लिए, और यही कारण है सी ++ बंद इस क्षमता प्रदान नहीं करते हैं।

+0

उत्कृष्ट प्रदर्शनी! –

+0

यह एक बहुत बड़ा सवाल जवाब देता है, मेरा इसका एक छोटा सा हिस्सा था! जिस पहेली को मैं याद कर रहा था उसका टुकड़ा प्रदान किया गया (डाउनवर्ड-अपवर्ड फ़ारग का उल्लेख विशेष रूप से सहायक था)। – gnarledRoot

+0

'डी' का उपयोग एक रोशनी बोनस है - एक और कारण मुझे खुशी है कि मैंने पाइथन में अपना नमूना कोड लिखा था। – gnarledRoot

1

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

क्या यह पर्याप्त स्पष्टीकरण है?

+1

_not_ रन टाइम के दौरान निर्धारित किया गया है? – Barmar

+0

ओओपीएस। मैंने शब्द बदल दिया और संपादन को पूरा नहीं किया। अब तय – Prune

1

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

तो यह शाब्दिक गुंजाइश श्रृंखला को काम करने के लिए स्थिर निर्धारित करने के लिए जो पहचानकर्ता जो समारोह में पाया जाएगा संभव है। कुछ गतिशील काम अभी भी किया जाना चाहिए, क्योंकि, आप मनमाने ढंग से घोंसला कार्य कर सकते हैं, इसलिए यदि फ़ंक्शन ए में फ़ंक्शन बी शामिल है जिसमें फ़ंक्शन सी शामिल है, तो फंक्शन सी के लिए फ़ंक्शन ए से एक चर का उपयोग करने के लिए, आपको सही फ्रेम ढूंढना होगा ए। (बंद करने के लिए वही बात।)

संबंधित मुद्दे