macros - लिस्प मैक्रोज़ इतना खास बनाता है क्या?




lisp homoiconicity (10)

प्रोग्रामिंग भाषाओं पर पॉल ग्राहम के निबंधों को पढ़ना एक सोचता है कि लिस्प मैक्रोज़ जाने का एकमात्र तरीका है। एक व्यस्त डेवलपर के रूप में, अन्य प्लेटफॉर्म पर काम करते हुए, मुझे लिस्प मैक्रोज़ का उपयोग करने का विशेषाधिकार नहीं मिला है। जो कोई भी बज़ समझना चाहता है, कृपया यह बताएं कि इस सुविधा को इतना शक्तिशाली बनाता है।

कृपया इसे पाइथन, जावा, सी # या सी विकास की दुनिया से समझने वाली किसी चीज़ से भी संबंधित करें।


आपको यहां लिस्पे मैक्रो के आसपास एक व्यापक बहस मिलेगी।

उस लेख का एक दिलचस्प सबसेट:

अधिकांश प्रोग्रामिंग भाषाओं में, वाक्यविन्यास जटिल है। मैक्रोज़ को प्रोग्राम सिंटैक्स को अलग करना है, इसका विश्लेषण करना है, और इसे फिर से इकट्ठा करना है। उनके पास प्रोग्राम के पार्सर तक पहुंच नहीं है, इसलिए उन्हें हेरिस्टिक्स और सर्वोत्तम अनुमानों पर निर्भर होना है। कभी-कभी उनका कट-रेट विश्लेषण गलत होता है, और फिर वे टूट जाते हैं।

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

यहां एक विस्तृत उदाहरण है। लिस्प में एक मैक्रो है, जिसे "सेटफ" कहा जाता है, जो असाइनमेंट करता है। सेटफ का सबसे सरल रूप है

  (setf x whatever)

जो "x" प्रतीक के मूल्य को "जो भी" अभिव्यक्ति के मान पर सेट करता है।

लिस्प में सूचियां भी हैं; आप क्रमशः सूची या शेष सूची का पहला तत्व प्राप्त करने के लिए "कार" और "सीडीआर" फ़ंक्शंस का उपयोग कर सकते हैं।

अब क्या होगा यदि आप किसी नए मूल्य के साथ सूची के पहले तत्व को प्रतिस्थापित करना चाहते हैं? ऐसा करने के लिए एक मानक कार्य है, और अविश्वसनीय रूप से, इसका नाम "कार" से भी बदतर है। यह "rplaca" है। लेकिन आपको "rplaca" याद रखना नहीं है, क्योंकि आप लिख सकते हैं

  (setf (car somelist) whatever)

somelist की कार सेट करने के लिए।

वास्तव में यहां क्या हो रहा है कि "setf" एक मैक्रो है। संकलन समय पर, यह अपने तर्कों की जांच करता है, और यह देखता है कि पहले व्यक्ति में फॉर्म (कार कुछ) है। यह खुद से कहता है "ओह, प्रोग्रामर कुछ की कार सेट करने की कोशिश कर रहा है। इसके लिए उपयोग करने वाला फ़ंक्शन 'rplaca' है।" और यह चुपचाप कोड को कोड में फिर से लिखता है:

  (rplaca somelist whatever)

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

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

इसका उपयोग "चीनी" सिंटैक्स को लागू करने के लिए किया जा सकता है जैसे कि for each स्टेटमेंट के for each -क्लोज़, linq select -expressions और इसी तरह, मैक्रोज़ जो अंतर्निहित कोड में बदल जाता है।

यदि जावा में मैक्रोज़ थे, तो आप मूल भाषा को बदलने के लिए सूर्य की आवश्यकता के बिना, जावा में लिंक सिंटैक्स को कार्यान्वित कर सकते हैं।

यहां छद्म कोड है कि सी # में लिस्प-स्टाइल मैक्रो का using करने के लिए कैसे using जा सकता है:

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

जबकि उपर्युक्त सभी बताते हैं कि मैक्रोज़ क्या हैं और यहां तक ​​कि अच्छे उदाहरण भी हैं, मुझे लगता है कि मैक्रो और सामान्य फ़ंक्शन के बीच महत्वपूर्ण अंतर यह है कि LISP फ़ंक्शन को कॉल करने से पहले सभी मानकों का मूल्यांकन करता है। एक मैक्रो के साथ यह विपरीत है, LISP मैक्रो के लिए अवांछित पैरामीटर पास करता है। उदाहरण के लिए, यदि आप किसी फ़ंक्शन में (+ 1 2) पास करते हैं, तो फ़ंक्शन मान 3 प्राप्त होगा। यदि आप इसे मैक्रो में पास करते हैं, तो उसे एक सूची (+ 1 2) प्राप्त होगी। इसका उपयोग सभी प्रकार की अविश्वसनीय रूप से उपयोगी सामग्री के लिए किया जा सकता है।

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

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0
    

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


मुझे यकीन नहीं है कि मैं हर किसी के (उत्कृष्ट) पदों में कुछ अंतर्दृष्टि जोड़ सकता हूं, लेकिन ...

Lisp मैक्रोज़ लिस्प वाक्यविन्यास प्रकृति की वजह से महान काम करते हैं।

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

संपादित करें: मैं जो कहने की कोशिश कर रहा था वह यह है कि लिस्प homoiconic , जिसका अर्थ है कि एक लिस्प प्रोग्राम के लिए डेटा संरचना लिस्प में ही लिखी जाती है।

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

व्यावहारिक रूप से, मैक्रोज़ के साथ आप अतिरिक्त भाषा या टूलिंग सीखने की आवश्यकता के बिना, और भाषा की पूरी शक्ति का उपयोग करने के बिना, लिस्प के शीर्ष पर अपनी छोटी-छोटी भाषा का निर्माण करते हैं।


मुझे यह सामान्य लिस्प कुकबुक से मिला लेकिन मुझे लगता है कि यह समझाया गया है कि क्यों lisp मैक्रोज़ अच्छे तरीके से अच्छे हैं।

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

(setq2 x y (+ z 3))

जब z=8 दोनों x और y 11 पर सेट होते हैं (मैं इसके लिए किसी भी उपयोग के बारे में नहीं सोच सकता, लेकिन यह सिर्फ एक उदाहरण है।)

यह स्पष्ट होना चाहिए कि हम setq2 को फ़ंक्शन के रूप में परिभाषित नहीं कर सकते हैं। यदि x=50 और y=-5 , यह फ़ंक्शन मान 50, -5, और 11 प्राप्त करेगा; यह नहीं पता होगा कि किस चर को सेट किया जाना चाहिए था। हम वास्तव में क्या कहना चाहते हैं, जब आप (लिस्प सिस्टम) देखते हैं (setq2 v1 v2 e) , इसे इसके बराबर (progn (setq v1 e) (setq v2 e)) । असल में, यह बिल्कुल सही नहीं है, लेकिन यह अभी के लिए करेगा। एक मैक्रो हमें आउटपुट पैटर्न (progn ...) में इनपुट पैटर्न (setq2 v1 v2 e) "को बदलने के लिए एक प्रोग्राम निर्दिष्ट करके ठीक से ऐसा करने की अनुमति देता है।"

अगर आपने सोचा कि यह अच्छा था तो आप यहां पढ़ सकते रहेंगे: http://cl-cookbook.sourceforge.net/macros.html


लिस्प मैक्रोज़ आपको यह तय करने की अनुमति देता है कि (यदि बिल्कुल) किसी भी भाग या अभिव्यक्ति का मूल्यांकन किया जाएगा। एक साधारण उदाहरण डालने के लिए, सी के बारे में सोचें:

expr1 && expr2 && expr3 ...

यह क्या कहता है: expr1 मूल्यांकन expr1 , और, यह सच होना चाहिए, expr2 मूल्यांकन expr2 आदि।

अब इसे एक और फ़ंक्शन में बनाने का प्रयास करें ... ठीक है, आप नहीं कर सकते। कुछ कॉल करना:

and(expr1, expr2, expr3)

expr1 1 झूठा था या नहीं, इस पर ध्यान दिए बिना जवाब देने से पहले सभी तीन exprs मूल्यांकन करेंगे!

लिस्प मैक्रोज़ के साथ आप कुछ इस तरह कोड कर सकते हैं:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

अब आपके पास एक && , जिसे आप केवल एक फ़ंक्शन की तरह कॉल कर सकते हैं और यह आपके द्वारा पारित किए जाने वाले किसी भी रूप का मूल्यांकन नहीं करेगा जब तक कि वे सभी सत्य न हों।

यह देखने के लिए कि यह कैसे उपयोगी है, इसके विपरीत:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

तथा:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

मैक्रोज़ के साथ आप अन्य चीजें कर सकते हैं नए कीवर्ड और / या मिनी-भाषाएं बना रहे हैं (उदाहरण के लिए (loop ...) मैक्रो देखें), अन्य भाषाओं को लिस्प में एकीकृत करना, उदाहरण के लिए, आप एक मैक्रो लिख सकते हैं जो आपको देता है कुछ ऐसा कहें:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

और वह रीडर मैक्रोज़ में भी नहीं पहुंच रहा है

उम्मीद है की यह मदद करेगा।


लिस्प मैक्रोज़ एक पैटर्न का प्रतिनिधित्व करता है जो लगभग किसी भी बड़े प्रोग्रामिंग प्रोजेक्ट में होता है। आखिरकार एक बड़े कार्यक्रम में आपके पास कोड का एक निश्चित अनुभाग होता है जहां आप महसूस करते हैं कि यह एक प्रोग्राम लिखने के लिए सरल और कम त्रुटि प्रवण होगा जो स्रोत कोड को टेक्स्ट के रूप में आउटपुट करता है जिसे आप बस पेस्ट कर सकते हैं।

पायथन वस्तुओं में दो विधियां __repr__ और __str____str__ बस मानव पठनीय प्रतिनिधित्व है। __repr__ एक प्रतिनिधित्व देता है जो वैध पायथन कोड है, जो कहने के लिए है, कुछ ऐसा है जिसे दुभाषिया में मान्य पायथन के रूप में दर्ज किया जा सकता है। इस तरह आप पाइथन के छोटे स्निपेट बना सकते हैं जो वैध कोड उत्पन्न करते हैं जिसे आपके वास्तविक स्रोत में चिपकाया जा सकता है।

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


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

उदाहरण के लिए, लिस्प में आप लिख सकते हैं

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

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


सामान्य लिस्प मैक्रोज़ अनिवार्य रूप से आपके कोड के "वाक्य रचनात्मक प्राइमेटिव" का विस्तार करते हैं।

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

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

सी में ऐसा करने के लिए, आपको एक कस्टम प्री-प्रोसेसर लिखना होगा जो आपके शुरुआती (काफी-सी) स्रोत को खाता है और सी संकलक समझ सकता है कि कुछ ऐसा थूकता है। इसके बारे में जाने का गलत तरीका नहीं है, लेकिन यह सबसे आसान नहीं है।