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




lisp elisp (8)

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

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

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

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

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

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


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

बस कर दो

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

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

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


जैसा कि पीटर अज़ताई ने बताया:

Emacs-24.1 के बाद से आप एक फ़ाइल के आधार पर लेक्सिकल स्कूपिंग को सक्षम कर सकते हैं

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

अपने elisp फ़ाइल के शीर्ष पर।


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

Emacs Lisp संदर्भ पढ़ें, आपके पास इस तरह के कई विवरण होंगे:

  • विशेष रूप: सेटक [प्रतीक रूप] ... यह विशेष रूप एक चर के मूल्य को बदलने का सबसे आम तरीका है। प्रत्येक SYMBOL को एक नया मान दिया जाता है, जो संबंधित FORM के मूल्यांकन का परिणाम है। प्रतीक के सबसे स्थानीय मौजूदा बंधन को बदल दिया जाता है

यहाँ एक उदाहरण है :

(defun foo () (setq tata "foo"))

(defun bar (tata) (setq tata "bar"))


(foo)
(message tata)
    ===> "foo"


(bar tata)
(message tata)
    ===> "foo"

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

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

(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 में समर्थित है। देखें: लेक्सिकल बाइंडिंग, ग्नू एमएसीएस लिस्प संदर्भ मैनुअल


मैं पूरी तरह से आपका दर्द महसूस करता हूं। मैं परेशान होने के बजाय emacs में lexical बाइंडिंग की कमी पाता हूं - विशेष रूप से lexical closures का उपयोग करने में सक्षम नहीं हो रहा है, जो एक समाधान मुझे लगता है कि मैं बहुत सोचता हूं, और अधिक आधुनिक भाषाओं से आ रहा हूं।

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


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

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

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

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

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

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

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


बस नहीं है।

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

(setq lexical-binding t)

या जोड़ें

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

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





scope