आम लिस्प में LET बनाम LET *


81

मैं एलईटी और एलईटी * (समानांतर बनाम अनुक्रमिक बंधन) के बीच अंतर को समझता हूं, और एक सैद्धांतिक मामले के रूप में यह बिल्कुल सही समझ में आता है। लेकिन क्या कोई ऐसा मामला है जहां आपको वास्तव में एलईटी की आवश्यकता है? मेरे सभी लिस्प कोड में जो मैंने हाल ही में देखा है, आप हर LET को LET * से बदल सकते हैं, जिसमें कोई बदलाव नहीं होगा।

संपादित करें: ठीक है, मैं समझता हूं कि कुछ लोगों ने LET * का आविष्कार क्यों किया, संभवतः एक मैक्रो के रूप में, जब वापस। मेरा प्रश्न है, यह देखते हुए कि LET * मौजूद है, क्या LET के आसपास रहने का कोई कारण है? क्या आपने कोई वास्तविक लिस्प कोड लिखा है जहां एक एलईटी * एक सादे एलईटी के रूप में भी काम नहीं करेगा?

मैं दक्षता तर्क नहीं खरीदता। सबसे पहले, ऐसे मामलों को पहचानना जहां LET * को कुछ के रूप में कुशल रूप में संकलित किया जा सकता है क्योंकि LET सिर्फ इतना कठिन नहीं लगता है। दूसरा, सीएल कल्पना में बहुत सी चीजें हैं जो बस ऐसा नहीं लगता कि वे दक्षता के चारों ओर डिजाइन किए गए थे। (जब पिछली बार आपने एलओओपी को प्रकार की घोषणाओं के साथ देखा था; तो यह पता लगाना बहुत कठिन है कि मैंने उन्हें कभी इस्तेमाल नहीं किया है।) डिक गेब्रियल के 1980 के दशक के अंत से पहले, सीएल बिल्कुल धीमा था

ऐसा लगता है कि यह पीछे की संगतता का एक और मामला है: बुद्धिमानी से, कोई भी एलईटी के रूप में मौलिक रूप से कुछ तोड़ने का जोखिम नहीं उठाना चाहता था। जो मेरा कूबड़ था, लेकिन यह सुनकर सुकून मिलता है कि किसी के पास मूर्खतापूर्ण-सरल मामला नहीं है जो मुझे याद आ रहा था जहां एलईटी ने एलईटी की तुलना में हास्यास्पद चीजों का एक गुच्छा बना दिया।


समानांतर शब्दों का घटिया विकल्प है; केवल पिछले बाइंडिंग दिखाई दे रहे हैं। समानांतर बंधन अधिक हास्केल के "... जहां ..." बाइंडिंग की तरह होगा।
जॉकवे

मुझे भ्रमित करने का लक्ष्य नहीं था; मेरा मानना ​​है कि वे शब्द हैं जिनका उपयोग युक्ति द्वारा किया जाता है। :-)
केन

12
समांतर सही है। इसका मतलब है कि बाइंडिंग एक ही समय में जीवन में आती है और एक-दूसरे को नहीं देखती है और एक-दूसरे को छाया नहीं देती है। किसी भी बिंदु पर एक उपयोगकर्ता-दृश्य वातावरण मौजूद नहीं है जिसमें LET में परिभाषित कुछ चर शामिल हैं, लेकिन अन्य नहीं।
काज

Haskells जहां बाइंडिंग Letrec की तरह अधिक हैं। वे एक ही दायरे के स्तर पर सभी बाइंडिंग देख सकते हैं।
एडगर क्लार्क ने

1
यह पूछना कि 'क्या कोई ऐसा मामला है जहाँ letज़रूरत है?' यह पूछने के लिए थोड़ा सा है कि 'क्या कोई ऐसा मामला है जहां एक से अधिक तर्क वाले कार्यों की आवश्यकता है?' letऔर let*दक्षता की कुछ धारणा के कारण वे मौजूद नहीं हैं क्योंकि वे प्रोग्रामिंग करते समय मनुष्यों को अन्य मनुष्यों के इरादे से संवाद करने की अनुमति देते हैं।
tfb

जवाबों:


86

LETएक कार्यात्मक प्रोग्रामिंग भाषा में स्वयं एक वास्तविक आदिम नहीं है , क्योंकि इसे प्रतिस्थापित किया जा सकता है LAMBDA। ऐशे ही:

(let ((a1 b1) (a2 b2) ... (an bn))
  (some-code a1 a2 ... an))

के समान है

((lambda (a1 a2 ... an)
   (some-code a1 a2 ... an))
 b1 b2 ... bn)

परंतु

(let* ((a1 b1) (a2 b2) ... (an bn))
  (some-code a1 a2 ... an))

के समान है

((lambda (a1)
    ((lambda (a2)
       ...
       ((lambda (an)
          (some-code a1 a2 ... an))
        bn))
      b2))
   b1)

आप कल्पना कर सकते हैं कि कौन सी सरल बात है। LETऔर नहीं LET*

LETकोड समझ को आसान बनाता है। किसी को बाइंडिंग का एक गुच्छा दिखाई देता है और कोई भी प्रत्येक 'बाइंडिंग' (प्रभाव) के शीर्ष-डाउन / लेफ्ट-राइट प्रवाह को समझने की आवश्यकता के बिना प्रत्येक बंधन को व्यक्तिगत रूप से पढ़ सकता है। LET*प्रोग्रामर (जो कोड पढ़ता है) को संकेतों का उपयोग करते हुए कि बाइंडिंग स्वतंत्र नहीं हैं, लेकिन किसी प्रकार का टॉप-डाउन प्रवाह है - जो चीजों को जटिल करता है।

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

में टाइप करेंLOOP ? अक्सर उपयोग किया जाता है। प्रकार की घोषणा के कुछ आदिम रूप हैं जिन्हें याद रखना आसान है। उदाहरण:

(LOOP FOR i FIXNUM BELOW (TRUNCATE n 2) do (something i))

ऊपर चर iको ए घोषित करता है fixnum

रिचर्ड पी। गेब्रियल ने 1985 में लिस्प बेंचमार्क पर अपनी पुस्तक प्रकाशित की थी और उस समय इन बेंचमार्क का उपयोग गैर-सीएल लिस्प्स के साथ भी किया गया था। 1985 में कॉमन लिस्प खुद बिल्कुल नया था - CLtL1 पुस्तक जिसने भाषा का वर्णन किया था वह 1984 में प्रकाशित हुई थी। कोई आश्चर्य नहीं कि उस समय कार्यान्वयन बहुत अनुकूलित नहीं थे। कार्यान्वित किए गए अनुकूलन मूल रूप से एक ही (या कम) थे जो कि कार्यान्वयन से पहले (जैसे MacLisp) थे।

लेकिन LETबनाम LET*मुख्य अंतर यह है कि कोड का उपयोग LETकरना मनुष्यों के लिए समझना आसान है, क्योंकि बाइंडिंग क्लॉज एक दूसरे से स्वतंत्र हैं - खासकर चूंकि यह बाएं से दाएं मूल्यांकन का लाभ लेने के लिए खराब शैली है (एक पक्ष के रूप में चर सेट नहीं करना) प्रभाव)।


3
नहीं नहीं! लैम्ब्डा एक वास्तविक आदिम नहीं है क्योंकि इसे एलईटी के साथ बदला जा सकता है, और एक निम्न-स्तरीय लैम्ब्डा जो सिर्फ तर्क मूल्यों को प्राप्त करने के लिए एक एपीआई प्रदान करता है: (low-level-lambda 2 (let ((x (car %args%)) (y (cadr args))) ...) :)
कज़

36

आपको LET की आवश्यकता नहीं है , लेकिन आप सामान्य रूप से इसे चाहते हैं।

एलईटी सुझाव देता है कि आप केवल मानक समानांतर बंधन कर रहे हैं और कुछ भी नहीं चल रहा है। LET * कंपाइलर पर प्रतिबंध को प्रेरित करता है और उपयोगकर्ता को सुझाव देता है कि एक कारण है कि अनुक्रमिक बाइंडिंग की आवश्यकता है। शैली के संदर्भ में , एलईटी बेहतर है जब आपको एलईटी * द्वारा लगाए गए अतिरिक्त प्रतिबंधों की आवश्यकता नहीं होती है।

यह LET की तुलना में LET * (कंपाइलर, ऑप्टिमाइज़र आदि के आधार पर) का उपयोग करने के लिए अधिक कुशल हो सकता है:

  • समानांतर बाइंडिंग को समानांतर में निष्पादित किया जा सकता है (लेकिन मुझे नहीं पता कि क्या कोई LISP सिस्टम वास्तव में ऐसा करता है, और init रूपों को अभी भी क्रमिक रूप से निष्पादित किया जाना चाहिए)
  • समानांतर बाइंडिंग सभी बाइंडिंग के लिए एक नया वातावरण (गुंजाइश) बनाती है। अनुक्रमिक बाइंडिंग हर एक बंधन के लिए एक नया नेस्टेड वातावरण बनाते हैं। समानांतर बाइंडिंग कम मेमोरी का उपयोग करती है और इसमें तेजी से परिवर्तनशील लुकअप होता है

(उपरोक्त बुलेट पॉइंट स्कीम पर लागू होते हैं, एक और LISP बोली। क्लिस्प भिन्न हो सकती है।)


2
ध्यान दें: इस उत्तर को देखें (और / या हाइपरस्पेक्ट से जुड़ा हुआ खंड) थोड़ा स्पष्टीकरण के लिए कि आपका पहला पल्लेट पॉइंट क्यों है - भ्रामक, आइए बताते हैं। बाइंडिंग समानांतर में होती हैं, लेकिन रूपों क्रमिक रूप से क्रियान्वित कर रहे हैं - कल्पना प्रति।
lindes

6
समानांतर निष्पादन कुछ ऐसा नहीं है जो कॉमन लिस्प मानक किसी भी तरह से देखभाल करता है। तेज परिवर्तनशील लुकअप भी एक मिथक है।
रेनर जोसविग

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

28

मैं आकस्मिक उदाहरण देते हुए आता हूं। इसके परिणाम की तुलना करें:

(print (let ((c 1))
         (let ((c 2)
               (a (+ c 1)))
           a)))

इसे चलाने के परिणाम के साथ:

(print (let ((c 1))
         (let* ((c 2)
                (a (+ c 1)))
           a)))

3
विकसित करने के लिए देखभाल क्यों यह मामला है?
जॉन मैकलेली

4
@ जॉन: पहले उदाहरण में, aबाध्यकारी बाहरी मूल्य को संदर्भित करता है c। दूसरे उदाहरण में, जहां let*बाइंडिंग को पिछले बाइंडिंग को संदर्भित करने की अनुमति मिलती है, बाइंडिंग aके आंतरिक मूल्य को संदर्भित करता है c। लोगन इस बारे में झूठ नहीं बोल रहा है एक उदाहरण है, और यह उपयोगी होने का दिखावा भी नहीं करता है। इसके अलावा, इंडेंटेशन गैर-मानक और भ्रामक है। दोनों में, a'बाइंडिंग' एक जगह होनी चाहिए , 'एस' के साथ लाइन अप करने के लिए c, और भीतर का 'बॉडी' खुद letसे सिर्फ दो जगह होना चाहिए let
zem

3
यह उत्तर एक महत्वपूर्ण अंतर्दृष्टि प्रदान करता है। जब आप द्वितीयक बाइंडिंग से बचना चाहते हैं, तो विशेष रूप से इसका उपयोग करेंगे (जिसके द्वारा मैं बस पहले एक का मतलब नहीं है) पहली बाइंडिंग को संदर्भित करता हूं , लेकिन क्या आप पिछली बाइंडिंग को छाया देना चाहते हैं - किसी एक को इनिशियलाइज़ करने के लिए इसके पिछले मूल्य का उपयोग करना आपकी माध्यमिक बाइंडिंग। let
8

2
यद्यपि इंडेंटेशन बंद है (और मैं इसे संपादित नहीं कर सकता), यह अब तक का सबसे अच्छा उदाहरण है। जब मैं देखता हूं (जाने दो ...) मैं जानता हूं कि कोई भी एक-दूसरे को बांधने वाला नहीं है और अलग-अलग व्यवहार किया जा सकता है। जब मैं देख रहा हूं (दो * ...) मैं हमेशा सावधानी के साथ संपर्क करता हूं और बहुत ध्यान से देखता हूं कि किन बिन्दुओं का फिर से उपयोग किया जा रहा है। इस कारण से, यह हमेशा समझ में आता है (चलो) जब तक आप बिल्कुल घोंसले के शिकार की जरूरत नहीं है।
एंड्रयू

1
(6 साल बाद ...) एक गुच्छे के रूप में, डिजाइन द्वारा गलत और भ्रामक इंडेंटेशन था ? मैं इसे ठीक करने के लिए इसे संपादित करने के लिए इच्छुक हूं ... क्या मुझे नहीं करना चाहिए?
विल नेस

11

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

हालांकि, केवल आपके कहने का वास्तविक दक्षता में सुधार हो सकता है, और मजबूत निर्माणों का उपयोग नहीं करना चाहिए। यदि आपके पास आरंभीकरण है LET, तो उन्हें समानांतर में निष्पादित किया जा सकता है, जबकि LET*इनिशियलाइज़ेशन को क्रमिक रूप से निष्पादित किया जाना है। मुझे नहीं पता कि क्या वास्तव में कोई भी कार्यान्वयन करेगा, लेकिन भविष्य में कुछ अच्छा हो सकता है।


2
अच्छी बात। यद्यपि लिस्प इतनी उच्च-स्तरीय भाषा है, लेकिन इससे मुझे आश्चर्य होता है कि लिस्प भूमि में "सबसे कमजोर संभव निर्माण" इस तरह की एक वांछित शैली क्यों है। आप पर्ल प्रोग्रामर्स को यह कहते हुए नहीं देख रहे हैं कि "हमें यहाँ regexp का उपयोग करने की आवश्यकता नहीं है ..." :-)
केन

1
मुझे नहीं पता, लेकिन एक निश्चित शैली वरीयता है। यह कुछ हद तक लोगों द्वारा विरोध किया जाता है (मेरे जैसे) जो संभव के रूप में एक ही रूप का उपयोग करना पसंद करते हैं (मैं लगभग सेट के बजाय सेटैक कभी नहीं लिखता हूं)। यह कहने के लिए कुछ हो सकता है कि आपको क्या मतलब है।
डेविड थॉर्नले

=ऑपरेटर न मजबूत है और न ही की तुलना में कमजोर है eql। यह एक कमजोर परीक्षा है क्योंकि 0इसके बराबर है 0.0। लेकिन यह मजबूत भी है क्योंकि गैर-संख्यात्मक तर्क खारिज कर दिए जाते हैं।
काज

2
आप जिस सिद्धांत का उल्लेख कर रहे हैं, वह सबसे मजबूत लागू होने वाले आदिम का उपयोग करना है , न कि सबसे कमजोर । उदाहरण के लिए, यदि तुलना की जा रही चीजें प्रतीक हैं, तो उपयोग करें eq। या यदि आप जानते हैं कि आप एक प्रतीकात्मक जगह का उपयोग करने के लिए कह रहे हैं setq। हालांकि, इस सिद्धांत को मेरे कई लिस्प प्रोग्रामर्स भी खारिज कर देते हैं, जो समय से पहले अनुकूलन के बिना एक उच्च स्तरीय भाषा चाहते हैं।
काज

1
वास्तव में, सीएलएचएस का कहना है कि " बाइंडिंग [समानांतर] की जाती है" लेकिन " इनिट -फॉर्म -1 , इनिट-फॉर्म -2 , और इसी तरह, [मूल्यांकन किया जाता है] में [विशिष्ट, बाएं से दाएं (या शीर्ष) -डाउन)] ऑर्डर ”। इसलिए मूल्यों को क्रमिक रूप से गणना की जानी चाहिए ( सभी मूल्यों की गणना के बाद बाइंडिंग स्थापित की जाती है)। समझ में भी आता है, क्योंकि RPLACD जैसी संरचना-उत्परिवर्तन भाषा का हिस्सा है और सही समानता के साथ यह गैर-नियतात्मक बन जाएगा।
विल नेस

9

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

(defun foo (a b)
  (let ((a (max a b))
        (b (min a b)))
    ; here we know b is not larger
    ...)
  ; we can use the original identities of a and b here
  ; (perhaps to determine the order of the results)
  ...)

मान bबड़ा था, अगर हम इस्तेमाल किया था let*, हम किसी सेट होता aहै और bएक ही मूल्य है।


1
जब तक आपको एक्स और वाई के मूल्यों की आवश्यकता नहीं होती है बाद में बाहरी के अंदर let, यह अधिक सरल (और स्पष्ट रूप से) के साथ किया जा सकता है: (rotatef x y)- एक बुरा विचार नहीं है, लेकिन यह अभी भी एक खिंचाव की तरह लगता है।
केन

यह सच है। यह अधिक उपयोगी हो सकता है अगर x और y विशेष चर होते।
शमूएल एडविन वार्ड

9

एलईटी और एलईटी * के बीच सामान्य सूची में मुख्य अंतर यह है कि एलईटी में प्रतीक समानांतर में और एलईटी में क्रमबद्ध रूप से बंधे हैं। एलईटी का उपयोग करने से इनिट-फॉर्म को समानांतर में निष्पादित नहीं होने दिया जाता है और न ही यह इनिट-फॉर्म के क्रम को बदलने की अनुमति देता है। कारण यह है कि आम लिस्प कार्यों को साइड-इफेक्ट करने की अनुमति देता है। इसलिए, मूल्यांकन का क्रम महत्वपूर्ण है और एक फॉर्म के भीतर हमेशा बाएं से दाएं होता है। इस प्रकार, एलईटी में, इन-फॉर्म का मूल्यांकन पहले, बाएं से दाएं, फिर बाइंडिंग का, समानांतर में बाएं से दाएं का मूल्यांकन किया जाता है। LET * में, init-form का मूल्यांकन किया जाता है और फिर अनुक्रम में बाएं से दाएं प्रतीक के लिए बाध्य होता है।

CLHS: विशेष संचालक LET, LET *


2
ऐसा लगता है कि इस सवाल का जवाब शायद के लिए एक प्रतिक्रिया होने से कुछ ऊर्जा आ रहा है इस सवाल का जवाब ? इसके अलावा, जुड़ी हुई युक्ति के अनुसार, बाइंडिंग को समानांतर में किया जाता है LET, भले ही आपको यह पुकारना सही हो कि इन-फॉर्म को श्रृंखला में निष्पादित किया गया है। किसी भी मौजूदा कार्यान्वयन में कोई व्यावहारिक अंतर है या नहीं, मुझे नहीं पता।
lindes

8

मैं एक कदम आगे और उपयोग जाना बाँध कि जोड़ता है let, let*, multiple-value-bind, destructuring-bindआदि, और यह और भी विस्तृत है।

आम तौर पर मुझे "सबसे कमजोर निर्माण" का उपयोग करना पसंद है, लेकिन letदोस्तों के साथ नहीं, क्योंकि वे कोड को शोर देते हैं (विषयगत चेतावनी! मुझे इसके विपरीत समझाने की कोशिश करने की आवश्यकता नहीं है ...)


ऊह, साफ-सुथरा। मैं अब BIND के साथ खेलने जा रहा हूं। लिंक के लिए धन्यवाद!
केन

4

संभवतः letसंकलक का उपयोग करके कोड को पुन: व्यवस्थित करने के लिए अधिक लचीलापन है, शायद अंतरिक्ष या गति में सुधार के लिए।

Stylistically, समानांतर बाइंडिंग का उपयोग करने का इरादा दिखाता है कि बाइंडिंग को एक साथ बांटा गया है; यह कभी-कभी गतिशील बाइंडिंग को बनाए रखने में उपयोग किया जाता है:

(let ((*PRINT-LEVEL* *PRINT-LEVEL*)
      (*PRINT-LENGTH* *PRINT-LENGTH*))
  (call-functions that muck with the above dynamic variables)) 

90% कोड में, इससे कोई फर्क नहीं पड़ता कि आप उपयोग करते हैं LETया नहीं LET*। इसलिए यदि आप उपयोग करते हैं *, तो आप एक अनावश्यक ग्लिफ़ जोड़ रहे हैं। यदि LET*समानांतर बांधने की मशीन थी और LETधारावाहिक बांधने वाले थे, तो प्रोग्रामर अभी भी उपयोग करेंगे LET, और LET*समानांतर बंधन चाहते समय केवल बाहर खींचेंगे। यह संभवतः LET*दुर्लभ बना देगा ।
कज़

वास्तव में, CLHS आइएट -फॉर्म s के मूल्यांकन के क्रम को निर्दिष्ट करता है
विल नेस्स

4
(let ((list (cdr list))
      (pivot (car list)))
  ;quicksort
 )

बेशक, यह काम करेगा:

(let* ((rest (cdr list))
       (pivot (car list)))
  ;quicksort
 )

और इस:

(let* ((pivot (car list))
       (list (cdr list)))
  ;quicksort
 )

लेकिन यह सोचा है कि मायने रखता है।


2

ओपी पूछता है "क्या वास्तव में कभी एलईटी की आवश्यकता थी"?

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

मैं LET को वरीयता में LET * का उपयोग करता हूं क्योंकि यह पाठक को कुछ बताता है कि डेटा प्रवाह कैसे सामने आ रहा है। मेरे कोड में, कम से कम, यदि आप एक एलईटी * देखते हैं तो आप जानते हैं कि जल्दी बंधे हुए मूल्यों को बाद में बाध्यकारी में उपयोग किया जाएगा। क्या मुझे ऐसा करने की "आवश्यकता" है, नहीं; लेकिन मुझे लगता है कि यह मददगार है। कहा कि मैंने पढ़ा है, शायद ही कभी, कोड जो कि LET * को चूकता है और LET संकेतों की उपस्थिति है कि लेखक वास्तव में यह चाहता था। उदाहरण के लिए दो संस्करणों का अर्थ स्वैप करने के लिए।

(let ((good bad)
     (bad good)
...)

बहस का परिदृश्य है जो 'वास्तविक आवश्यकता' के निकट है। यह मैक्रोज़ के साथ उठता है। यह मैक्रो:

(defmacro M1 (a b c)
 `(let ((a ,a)
        (b ,b)
        (c ,c))
    (f a b c)))

से बेहतर काम करता है

(defmacro M2 (a b c)
  `(let* ((a ,a)
          (b ,b)
          (c ,c))
    (f a b c)))

चूंकि (M2 cba) काम नहीं कर रहा है। लेकिन उन मैक्रो मिश्रित कारणों के लिए बहुत मैला हैं; ताकि 'वास्तविक जरूरत' तर्क को कमजोर किया जा सके।


1

रेनर जोसविग के उत्तर के अलावा , और एक शुद्ध या सैद्धांतिक दृष्टिकोण से। Let & Let * दो प्रोग्रामिंग प्रतिमानों का प्रतिनिधित्व करते हैं; क्रमशः कार्यात्मक और अनुक्रमिक।

जैसा कि मुझे लेट * के बजाय लेट * का उपयोग करने के लिए क्यों करना चाहिए, ठीक है, आप घर से आने का मज़ा ले रहे हैं और शुद्ध कार्यात्मक भाषा में सोच रहे हैं, जैसा कि अनुक्रमिक भाषा के विपरीत है, जहां मैं अपना अधिकतर दिन काम के साथ बिताता हूं :)


1

के साथ आप समानांतर बंधन का उपयोग करते हैं,

(setq my-pi 3.1415)

(let ((my-pi 3) (old-pi my-pi))
     (list my-pi old-pi))
=> (3 3.1415)

और लेट * सीरियल बाइंडिंग के साथ,

(setq my-pi 3.1415)

(let* ((my-pi 3) (old-pi my-pi))
     (list my-pi old-pi))
=> (3 3)

हां, यह है कि वे कैसे परिभाषित हैं। लेकिन आपको पूर्व की आवश्यकता कब होगी? आप वास्तव में एक प्रोग्राम नहीं लिख रहे हैं, जिसे एक विशेष क्रम में पीआई के मूल्य को बदलने की आवश्यकता है, मुझे लगता है। :-)
केन

1

letऑपरेटर सभी बाइंडिंग जो यह निर्दिष्ट करता है के लिए एक एकल वातावरण प्रस्तुत करता है। let*, कम से कम वैचारिक रूप से (और अगर हम एक पल के लिए घोषणाओं को नजरअंदाज करते हैं) कई वातावरण पेश करते हैं:

यानी:

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

के समान ही:

(let (a) (let (b) (let (c) ...)))

तो एक अर्थ में, letअधिक आदिम है, जबकि -s का let*झरना लिखने के लिए एक वाक्यगत चीनी है let

लेकिन ऐसा कभी नहीं हुआ। (और हम बाद में औचित्य देंगे क्योंकि हमें "कभी बुरा नहीं मानना ​​चाहिए")। तथ्य यह है कि दो ऑपरेटर हैं, और कोड के "99%" में, इससे कोई फर्क नहीं पड़ता कि आप किसका उपयोग करते हैं। letअधिक पसंद करने का कारण let*यह है कि इसमें *अंत में झूलने की क्षमता नहीं है ।

जब भी आपके पास दो ऑपरेटर होते हैं, और किसी के *नाम पर झूलना होता है, *तो बिना किसी का उपयोग किए यदि यह उस स्थिति में काम करता है, तो अपने कोड को कम बदसूरत रखने के लिए।

इसके लिए वहां यही सब है।

यह कहा जा रहा है, मुझे संदेह है कि अगर letऔर let*उनके अर्थों का आदान - प्रदान किया जाता है, तो यह संभवतः let*अब की तुलना में उपयोग करने के लिए अधिक दुर्लभ होगा । सीरियल बाइंडिंग व्यवहार सबसे कोड को परेशान नहीं करेगा जिसकी आवश्यकता नहीं है: शायद ही कभी let*समानांतर व्यवहार का अनुरोध करने के लिए इस्तेमाल किया जाना होगा (और ऐसी स्थितियों को छायांकन से बचने के लिए चर का नाम बदलकर भी तय किया जा सकता है)।

अब उस वादे पर चर्चा के लिए। हालाँकि let*वैचारिक रूप से कई वातावरण पेश करते हैं, लेकिन let*निर्माण को संकलित करना बहुत आसान है ताकि एक एकल वातावरण उत्पन्न हो। इसलिए भले ही (कम से कम अगर हम एएनएसआई सीएल घोषणाओं की उपेक्षा करते हैं), एक बीजीय समानता है जो एक एकल let*कई नेस्टेड let-s से मेल खाती है , जो letअधिक आदिम दिखती है, वास्तव let*में letइसे संकलित करने के लिए विस्तार करने का कोई कारण नहीं है, और यहां तक ​​कि बुरा विचार।

एक और बात: ध्यान दें कि कॉमन लिस्प lambdaवास्तव में समान let*शब्दार्थ का उपयोग करता है ! उदाहरण:

(lambda (x &optional (y x) (z (+1 y)) ...)

यहाँ, पहले पैरामीटर तक पहुँचने के xलिए init-form , और इसी तरह पहले के वैकल्पिक को संदर्भित करता है । भाषा के इस क्षेत्र में, बाइंडिंग शो में अनुक्रमिक दृश्यता के लिए एक स्पष्ट प्राथमिकता। यदि पैरामीटर नहीं देख सकते हैं तो यह कम उपयोगी होगा ; एक वैकल्पिक पैरामीटर पिछले मापदंडों के मूल्य के संदर्भ में संक्षिप्त नहीं हो सकता है।yx(+1 y)ylambda


0

के तहत let, सभी चर प्रारंभिक अभिव्यक्तियाँ बिल्कुल एक ही शाब्दिक वातावरण को देखती हैं: जो कि चारों ओर से घेरे हुए है let। यदि वे अभिव्यक्तियां शाब्दिक क्लोजर को पकड़ने के लिए होती हैं, तो वे सभी एक ही पर्यावरण ऑब्जेक्ट को साझा कर सकते हैं।

के तहत let*, हर शुरुआती अभिव्यक्ति एक अलग वातावरण में होती है। प्रत्येक क्रमिक अभिव्यक्ति के लिए, एक नया वातावरण बनाने के लिए पर्यावरण को बढ़ाया जाना चाहिए। कम से कम अमूर्त शब्दार्थ में, यदि क्लोज़र कैप्चर किए जाते हैं, तो उनके पास अलग-अलग पर्यावरणीय ऑब्जेक्ट होते हैं।

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

(यह सच है भले ही let*सिर्फ एक मैक्रो ऑपरेटर जो कैस्केड letरूपों का उत्सर्जन करता है; अनुकूलन उन कैस्केड letएस पर किया जाता है )।

आप let*एक ही भोले के रूप में लागू नहीं कर सकते हैं let, छिपे हुए चर असाइनमेंट के साथ इनिशियलाइज़ेशन करना क्योंकि उचित स्कूपिंग की कमी सामने आएगी:

(let* ((a (+ 2 b))  ;; b is visible in surrounding env
       (b (+ 3 a)))
  forms)

यदि इसे चालू कर दिया जाए

(let (a b)
  (setf a (+ 2 b)
        b (+ 3 a))
  forms)

यह इस मामले में काम नहीं करेगा; आंतरिक bबाहरी छाया कर रहा है bइसलिए हम 2 को जोड़ते हैं nil। इस प्रकार का परिवर्तन किया जा सकता है यदि हम इन सभी चर का अल्फा-नाम बदलें। पर्यावरण तो अच्छी तरह से चपटा है:

(let (#:g01 #:g02)
  (setf #:g01 (+ 2 b) ;; outer b, no problem
        #:g02 (+ 3 #:g01))
  alpha-renamed-forms) ;; a and b replaced by #:g01 and #:g02

उसके लिए हमें डिबग समर्थन पर विचार करने की आवश्यकता है; यदि प्रोग्रामर डिबगर के साथ इस शाब्दिक दायरे में कदम रखता है, तो क्या हम चाहते हैं कि वे #:g01इसके बजाय व्यवहार करें a

तो मूल रूप से, let*जटिल निर्माण है जिसे अच्छी तरह से प्रदर्शन करने के लिए अनुकूलित किया जाना चाहिए और साथ ही letऐसे मामलों में जब यह कम हो सकता है let

अकेले पक्ष औचित्य नहीं होता है कि letअधिक let*। मान लेते हैं कि हमारे पास एक अच्छा संकलक है; let*हर समय उपयोग क्यों नहीं ?

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

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


-1

मैं ज्यादातर एलईटी का उपयोग करता हूं, जब तक कि मुझे विशिष्ट रूप से एलईटी * की आवश्यकता नहीं होती है, लेकिन कभी-कभी मैं कोड लिखता हूं जो स्पष्ट रूप से एलईटी की आवश्यकता होती है , आमतौर पर जब मिश्रित (आमतौर पर जटिल) डिफ़ॉल्ट रूप से किया जाता है। दुर्भाग्य से, मेरे पास हाथ में कोई भी कोड उदाहरण नहीं है।


-1

जो फिर से लेफ्ट * लेफ्ट * को फिर से लिखने जैसा महसूस करता है? खोलना-रक्षा कॉल की संख्या?

अनुक्रमिक बाइंडिंग को अनुकूलित करना आसान है।

शायद यह env को प्रभावित करता है ?

गतिशील सीमा के साथ निरंतरता की अनुमति देता है?

कभी-कभी (लेट (xyz) (सेटक z 0 y 1 x (+ (सेट x x)) (prog1 (+ xy) (सेट x (1- x)))) (मान) ())

[मुझे लगता है कि काम करता है] बिंदु है, सरल कभी-कभी पढ़ने में आसान होता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.