Emacs लिस्प डायनेमिक स्कूपिंग के साथ कैसे रहें?




lisp elisp (7)

मैंने क्लोजर को पहले और वास्तव में भाषा की तरह सीखा है। मैं भी Emacs से प्यार करता हूं और Emacs Lisp के साथ कुछ सरल चीजें हैक कर चुका हूं। एक बात और है जो मुझे मानसिक रूप से एलिस्प के साथ कुछ भी अधिक करने से रोकती है। यह गतिशील स्कूपिंग की अवधारणा है। मैं इसके बारे में डर रहा हूँ क्योंकि यह मेरे लिए बहुत अलग है और अर्ध-वैश्विक चर की तरह खुशबू आ रही है।

तो चर घोषणाओं के साथ मुझे नहीं पता कि कौन सी चीजें करना सुरक्षित हैं और कौन सी खतरनाक हैं। मैंने जो समझा है, से सेटबैक वाले वेरिएबल्स डायनेमिक स्कूपिंग के तहत आते हैं (क्या यह सही है?) लेट वेरिएबल्स के बारे में क्या? कहीं मैंने पढ़ा है कि आप को प्लेन लेक्सिकल स्कूपिंग करने की अनुमति देता है, लेकिन कहीं और मैंने पढ़ा कि वार्स को भी गतिशील रूप से स्कैन किया जाता है।

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

क्या अंगूठे के कुछ सरल नियम हैं जिनका मैं बस अनुसरण कर सकता हूं और यह जान सकता हूं कि कुछ अजीब, कठिन-से-डिबग तरीके से काटे बिना गुंजाइश के साथ क्या होता है?


अन्य उत्तर तकनीकी विवरणों को समझाने के लिए अच्छे हैं कि कैसे गतिशील स्कूपिंग के साथ काम किया जाए, इसलिए यहां मेरी गैर-तकनीकी सलाह है:

बस कर दो

मैं 15+ वर्षों के लिए Emacs लिस्प के साथ छेड़छाड़ कर रहा हूं और यह नहीं जानता कि मैंने कभी भी किसी भी समस्या के कारण शाब्दिक / गतिशील दायरे के अंतर के कारण काट लिया है।

व्यक्तिगत रूप से, मुझे क्लोजर की आवश्यकता नहीं है (मैं उन्हें प्यार करता हूं, बस उन्हें एमएसीएस की आवश्यकता नहीं है)। और, मैं आम तौर पर सामान्य रूप से वैश्विक चरों से बचने की कोशिश करता हूं (चाहे स्कूपिंग शाब्दिक या गतिशील थी)।

इसलिए मैं सुझाव देता हूं कि आप अपनी आवश्यकताओं / इच्छाओं के अनुरूप कूदने और अनुकूलन करने का सुझाव दें, संभावना है कि आपको कोई समस्या नहीं होगी।


गाइल्स उत्तर के अंतिम पैराग्राफ के अलावा, यहाँ बताया गया है कि आरएमएस एक एक्स्टेंसिबल सिस्टम में डायनेमिक स्कूपिंग के पक्ष में कैसे तर्क देता है:

कुछ भाषा डिजाइनरों का मानना ​​है कि गतिशील बंधन से बचा जाना चाहिए, और इसके बजाय स्पष्ट तर्क पास का उपयोग किया जाना चाहिए। कल्पना करें कि फ़ंक्शन A, वेरिएबल FOO को बांधता है, और फ़ंक्शन B को कॉल करता है, जो फ़ंक्शन C को कॉल करता है, और C FOO के मान का उपयोग करता है। माना जाता है कि A को B के तर्क के रूप में मान पास करना चाहिए, जिसे C के तर्क के रूप में पारित करना चाहिए।

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

क्या बुरा है, सी को भी एक अतिरिक्त तर्क दिया जाना चाहिए। B, C को नाम से संदर्भित नहीं करता (C तब मौजूद नहीं था जब B लिखा गया था)। यह संभवतः कमांड प्रेषण तालिका में C के लिए एक पॉइंटर पाता है। इसका मतलब यह है कि एक ही कॉल जिसे कभी-कभी C कहते हैं, समान रूप से किसी भी संपादक कमांड परिभाषा को समान रूप से कॉल कर सकता है। तो सभी एडिटिंग कमांड को अतिरिक्त तर्क को स्वीकार करने और अनदेखा करने के लिए फिर से लिखना होगा। अब तक, कोई भी मूल प्रणाली नहीं बची है!

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

आरएमएस को फिर से उद्धृत करने के लिए:

डायनेमिक स्कोप के लिए केवल स्कोप नियम उपलब्ध होना आवश्यक नहीं है, बस उपलब्ध होने के लिए यह उपयोगी है।


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

  • एक फ़ंक्शन वैश्विक चर को छाया देता है, फिर एक अन्य फ़ंक्शन को कॉल करता है जो उस वैश्विक चर का उपयोग करता है।

    (defvar x 3)
    (defun foo ()
      x)
    (defun bar (x)
      (+ (foo) x))
    (bar 0)  0

    यह अक्सर Emacs में नहीं आता है क्योंकि स्थानीय चर छोटे नाम (अक्सर एकल-शब्द) होते हैं, जबकि वैश्विक चर लंबे नाम होते हैं (अक्सर packagename- द्वारा उपसर्ग packagename- )। कई मानक फ़ंक्शंस में ऐसे नाम होते हैं जो list और point जैसे स्थानीय चर के रूप में उपयोग करने के लिए लुभाते हैं, लेकिन फ़ंक्शंस और चर अलग-अलग नाम रिक्त स्थान में रहते हैं स्थानीय फ़ंक्शन बहुत बार उपयोग नहीं किए जाते हैं।

  • एक फ़ंक्शन को एक शाब्दिक संदर्भ में परिभाषित किया गया है और इस शाब्दिक संदर्भ के बाहर उपयोग किया जाता है क्योंकि यह एक उच्च-क्रम फ़ंक्शन के लिए पारित किया गया है।

    (let ((cl-y 10))
      (mapcar* (lambda (elt) (* cl-y elt)) '(1 2 3)))
     (10 20 30)
    (let ((cl-x 10))
      (mapcar* (lambda (elt) (* cl-x elt)) '(1 2 3)))
     (wrong-type-argument number-or-marker-p (1 2 3))

    त्रुटि mapcar* ( cl पैकेज से) चर नाम के रूप में cl-x के उपयोग के कारण है। ध्यान दें कि उच्च क्रम के कार्यों में भी cl पैकेज अपने स्थानीय चर के लिए उपसर्ग के रूप में cl- का उपयोग करता है। जब तक आप एक वैश्विक नाम के रूप में और स्थानीय नाम के रूप में एक ही चर का उपयोग नहीं करने का ख्याल रखते हैं, तब तक यह यथोचित रूप से अच्छी तरह से काम करता है, और आपको एक पुनरावर्ती उच्च-क्रम फ़ंक्शन लिखने की आवश्यकता नहीं है।

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


बस नहीं है।

Emacs-24 आपको लेक्सिकल-स्कोप का उपयोग करने देता है। बस दौडो

(setq lexical-binding t)

या जोड़ें

;; -*- lexical-binding: t -*-

आपकी फ़ाइल की शुरुआत में।


यहाँ जो कुछ भी लिखा गया है वह सब कुछ सार्थक है। मैं इसे जोड़ूंगा : कॉमन लिस्प का पता लगाऊं - अगर और कुछ नहीं, तो इसके बारे में पढ़ें। CLTL2 लेक्सिकल और डायनेमिक बाइंडिंग को अच्छी तरह से प्रस्तुत करता है, जैसा कि अन्य किताबें करती हैं। और कॉमन लिस्प उन्हें एकल भाषा में अच्छी तरह से एकीकृत करता है।

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

इसलिए मेरा सुझाव यह जानना है कि कॉमन लिस्प इससे कैसे निपटता है। स्कीम के बारे में भूलने की कोशिश करें, यदि वह आपका लिस्प का मुख्य मानसिक मॉडल है - तो यह Emacs Lisp में आपको स्कूपिंग, फ़नकार आदि को समझने में आपकी मदद करने से ज्यादा सीमित कर देगा। Emacs Lisp, आम Lisp की तरह, "गंदा और कम-डाउन" है; यह स्कीम नहीं है।


सबसे पहले, elisp में अलग-अलग वैरिएबल और फंक्शन बाइंडिंग होते हैं, इसलिए डायनेमिक स्कूपिंग के कुछ नुकसान प्रासंगिक नहीं हैं।

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

lexical-let , एक मैक्रो है (अनिवार्य रूप से) लेक्सिकल बाइंडिंग की नकल करता है (मेरा मानना ​​है कि यह शरीर पर चलने से होता है और लेक्सिकली के सभी घटनाओं को बदलकर एक gensymmed नाम को वेरिएबल करता है, अंततः प्रतीक को एकतरफा कर सकता है), अगर आपको बिल्कुल ज़रूरत है।

मैं कहूंगा कि "कोड को सामान्य लिखें"। ऐसे कई बार होते हैं जब आप के लिए प्रकृति की गतिशील प्रकृति आपको काटती है, लेकिन मैंने पाया है कि व्यवहार में यह आश्चर्यजनक रूप से दुर्लभ है।

यहाँ एक उदाहरण है कि मैं सेटक और गतिशील रूप से बाध्य चर के बारे में क्या कह रहा था (हाल ही में एक खरोंच बफर में मूल्यांकन किया गया है):

(let ((a nil))
  (list (let ((a nil))
          (setq a 'value)
          a)
        a))

(value nil)

यह बुरा नहीं है।

कार्यों में मुख्य समस्याएं 'मुक्त चर' के साथ दिखाई दे सकती हैं।

(defun foo (a)
  (* a b))

उपरोक्त फ़ंक्शन में एक स्थानीय चर है। b एक निःशुल्क चर है। Emacs Lisp जैसे डायनेमिक बाइंडिंग वाले सिस्टम में b को रनटाइम पर देखा जाएगा। अब तीन मामले हैं:

  1. b को परिभाषित नहीं किया गया है -> त्रुटि
  2. b वर्तमान गतिशील दायरे में कुछ फ़ंक्शन कॉल द्वारा बाध्य स्थानीय चर है -> उस मान को लें
  3. b एक वैश्विक चर है -> उस मान को लें

समस्याएं तब हो सकती हैं:

  • एक बाउंड वैल्यू (वैश्विक या स्थानीय) एक फ़ंक्शन कॉल द्वारा छाया हुआ है, संभवतः अवांछित है
  • अपरिभाषित चर को छायांकित नहीं किया जाता है -> पहुँच में त्रुटि
  • एक वैश्विक वैरिएबल छाया नहीं है -> वैश्विक मूल्य उठाता है, जो अवांछित हो सकता है

संकलक के साथ एक लिस्प में, उपरोक्त फ़ंक्शन को संकलित करना एक चेतावनी उत्पन्न कर सकता है कि एक मुफ्त चर है। आमतौर पर आम लिस्प कंपाइलर ऐसा करेंगे। एक दुभाषिया उस चेतावनी को प्रदान नहीं करेगा, एक बस रनटाइम पर प्रभाव देखेगा।

सलाह :

  • सुनिश्चित करें कि आप गलती से मुफ्त चर का उपयोग नहीं करते हैं
  • सुनिश्चित करें कि वैश्विक चर का एक विशेष नाम है, ताकि वे स्रोत कोड में आसानी से हाजिर हों, आमतौर पर *foo-var*

लिखो मत

(defun foo (a b)
   ...
   (setq c (* a b))  ; where c is a free variable
   ...)

लिखो:

(defun foo (a b)
   ...
   (let ((c (* a b)))
     ...)
   ...)

उन सभी चरों को बांधें जिनका आप उपयोग करना चाहते हैं और आप यह सुनिश्चित करना चाहते हैं कि वे कहीं और बंधे नहीं हैं।

वह मूल रूप से यह है।

चूंकि GNU Emacs संस्करण 24 लेक्सिकल बाइंडिंग इसके Emacs Lisp में समर्थित है। देखें: लेक्सिकल बाइंडिंग, ग्नू एमएसीएस लिस्प संदर्भ मैनुअल





scope