2012-03-01 10 views
15

यह सवाल है परिणाम के लिए एक: Editing programs “while they are running”? Why?संपादन कार्यक्रम "जब वे चल रहे हैं"? कैसे?

मैं हाल ही में Clojure की दुनिया से अवगत कराया जा रहा है और द्वारा afewexamples मैं "लाइव कोडिंग" का देखा है मोहित कर रहा हूँ। उपरोक्त लिंक "क्यों" पर चर्चा करता है।

मेरा प्रश्न है: कैसे यह लाइव कोडिंग तकनीक संभव है? क्या यह क्लोजर भाषा की विशेषता है जो इसे संभव बनाता है? या यह सिर्फ एक पैटर्न है जिसे उन्होंने लागू किया है जिसे किसी भी भाषा पर लागू किया जा सकता है? मुझे अजगर और जावा में पृष्ठभूमि मिली है। क्या इन भाषाओं में से किसी एक में "लाइव कोड" करना संभव होगा जैसे क्लोजर में यह संभव है?

+0

प्रासंगिक: https://en.wikipedia.org/wiki/Dynamic_software_updating – user

उत्तर

6

JRebel जावा के लिए एक समाधान है। यहां उनके FAQ:

जेआरबीएल मुख्य रूप से कक्षा लोडर स्तर पर JVM और एप्लिकेशन सर्वर के साथ एकीकृत करता है। यह किसी भी नए वर्ग लोडर नहीं बनाता है, इसके बजाय, यह मौजूदा लोगों को पुनः लोड किए गए वर्गों को प्रबंधित करने की क्षमता के साथ बढ़ाता है।

3

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

2

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

कंप्यूटर में, कोड और डेटा मेमोरी में मौजूद है। प्रोग्रामिंग भाषाओं में, हम स्मृति के उन "भाग" को संदर्भित करने के लिए नामों का उपयोग करते हैं।

मेमोरी "ए" की कुछ बाइट्स "नाम" देगा। यह भी प्रकार सिस्टम के आधार पर 0 पर "निर्दिष्ट" होता है कि स्मृति बाइट मूल्य इसी,

int add(int first, int second) { 
    return first + second; 
} 

"नाम" स्मृति के कुछ बाइट्स से "जोड़ें" होगा। यह उस स्मृति को दो "int" संख्याओं के लिए कॉल स्टैक को देखने के लिए मशीन निर्देशों को शामिल करने के लिए "असाइन" करेगा, उन्हें एक साथ जोड़ देगा, और परिणाम को कॉल स्टैक पर उचित स्थान पर रखेगा।

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

जावा में, सभी प्रकार "हस्ताक्षर" को हल करते हैं जो विधि नाम और "प्रकार" का एक स्ट्रिंग प्रतिनिधित्व है। प्रदान की ऐड उदाहरण को देखते हुए, हस्ताक्षर

// This has a signature of "add(I,I)I" 
int add(int first, int second) { 
    return first + second; 
} 

तो जावा समर्थित (के रूप में Clojure करता है) विधि नाम असाइनमेंट, वह अपने घोषित प्रकार प्रणाली के नियमों पर विस्तार, और विधि नाम असाइनमेंट अनुमति देने के लिए होता है।विधि असाइनमेंट की एक नकली उदाहरण तार्किक तरह

subtract = add; 

लगेगा लेकिन यह एक जोरदार लिखा जाता है (जावा मैच के लिए) "प्रकार" के साथ, घटाना घोषित करने के लिए की जरूरत की आवश्यकता होगी।

public subtract(I,I)I; 

और बिना किसी देखभाल के, ऐसी घोषणाएं आसानी से भाषा के पहले से परिभाषित भागों पर चल सकती हैं।

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

9

कुछ भाषा कार्यान्वयन में लंबे समय तक, विशेष रूप से कई लिस्प संस्करण और स्मॉलटाक हैं।

लिस्प में डेटा संरचना के रूप में पहचानकर्ता हैं, जिन्हें प्रतीकों कहा जाता है। इन प्रतीकों को फिर से सौंप दिया जा सकता है और वे रनटाइम पर देखे जाते हैं। इस सिद्धांत को देर से बाध्यकारी कहा जाता है। प्रतीकों का नाम फ़ंक्शन और चर।

इसके अतिरिक्त लिस्प कार्यान्वयन या तो क्रम एक दुभाषिया या यहां तक ​​कि एक संकलक पर है। इंटरफ़ेस EVAL और COMPILE फ़ंक्शन हैं। इसके अलावा एक समारोह LOAD है, जो स्रोत कोड और संकलित कोड लोड करने की अनुमति देता है।

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

यह भी महत्वपूर्ण है कि लिस्प कार्यान्वयन कचरा संग्रहण हटाया गया कोड हो सकता है। इस तरह चलने वाला लिस्प रनटाइम आकार में नहीं बढ़ेगा क्योंकि कोड बदल दिया गया है।

लिस्प में अक्सर एक त्रुटि प्रणाली होती है जो त्रुटियों से ठीक हो सकती है और डीबगर के भीतर से दोषपूर्ण कोड को प्रतिस्थापित करने की अनुमति देती है।

+0

लिस्प क्या करता है (उचित शब्दावली के साथ) का एक अच्छा विवरण, लेकिन "कैसे" भाग पर थोड़ा सा प्रकाश। –

+1

@ एडविन बक: इसके लिए मैं किसी भी तरह की लिस्प कार्यान्वयन की व्याख्या करने वाली बेहतर पुस्तकों को पढ़ने की सलाह देता हूं: छोटे टुकड़ों, एसआईसीपी, पीएआईपी, में लिस्प ... –

2

यह कई भाषाओं में संभव है, लेकिन आप निम्नलिखित विशेषताएं हैं केवल यदि:

  • आरईपीएल या तो समान रूप से कुछ आपके द्वारा चलाए जा पर्यावरण
  • नाम स्थान से कुछ फार्म का है कि संशोधित किया जा सकता के साथ बातचीत कर सकते हैं रनटाइम पर
  • गतिशील, नाम स्थान के खिलाफ बाध्यकारी ताकि यदि आप नाम स्थान में आइटम बदल तो जिक्र कोड स्वचालित रूप से परिवर्तन उठाता

लिस्प/क्लोजर इन सभी को डिफ़ॉल्ट रूप से बनाया गया है, जो कि लिस्प दुनिया में विशेष रूप से प्रमुख क्यों है, इसका एक कारण है।

उदाहरण इन सुविधाओं (Clojure REPL सभी) का प्रदर्शन:

; define something in the current namespace 
(def y 1) 

; define a function which refers to y in the current namespace 
(def foo [x] (+ x y)) 

(foo 10) 
=> 11 

; redefine y 
(def y 5) 

; prove that the change was picked up dynamically 
(foo 10) 
=> 15 
4

वहाँ अच्छा जवाब का एक बहुत यहाँ हैं, और मुझे यकीन है कि मैं उनमें से किसी पर सुधार कर सकते हैं नहीं कर रहा हूँ, लेकिन मैं चाहता था क्लोजर और जावा के आसपास कुछ टिप्पणियां जोड़ें।

सबसे पहले, क्लोजर जावा में लिखा गया है, ताकि आप निश्चित रूप से जावा में लाइव-कोडिंग वातावरण बना सकें। बस क्लोजर को लाइव कोडिंग वातावरण के विशिष्ट स्वाद के रूप में सोचें।

मूल रूप से, क्लोजर में लाइव कोडिंग मुख्य.क्लोज में पढ़ने के फ़ंक्शन के माध्यम से काम करता है और core.clj में eval फ़ंक्शन (src/clj/clojure/main.clj और src/clj/clojure/core.clj github में भंडार)। आप फॉर्म में पढ़ते हैं और उन्हें eval करने के लिए पास करते हैं, जो clojure.lang.Compiler (repo में src/jvm/clojure/lang/compiler.java) को कॉल करता है।

कंपाइलर.जावा एएसएम लाइब्रेरी (ASM website here, documentation here) का उपयोग कर क्लोजर फॉर्म को जेवीएम बाइटकोड में परिवर्तित करता है। मुझे यकीन नहीं है कि एएसएम लाइब्रेरी का संस्करण क्लोजर द्वारा उपयोग किया जाता है। यह बाइटकोड (बाइट्स => बाइट [] बाइटकोड की एक सरणी कंपाइलर क्लास का सदस्य है जो आखिरकार clojure.asm.ClassWriter क्लास द्वारा क्लासवाइटर # toByteArray के माध्यम से उत्पन्न बाइट्स को पकड़ लेगी) को तब कक्षा में परिवर्तित किया जाना चाहिए और इसमें लिंक होना चाहिए चल रही प्रक्रिया।

एक बार जब आप बाइट सरणी के रूप में कक्षा का प्रतिनिधित्व कर लेते हैं, तो यह java.lang.ClassLoader को पकड़ने का विषय है, परिभाषित क्लास को कक्षा में उन बाइट्स को चालू करने के लिए कॉल करना, और उसके बाद परिणामी कक्षा को हल करना जावा रनटाइम से लिंक करने के लिए क्लासलोडर की विधि। यह मूल रूप से होता है जब आप एक नया फ़ंक्शन परिभाषित करते हैं, और आप कंपाइलर $ FnExpr में कंपाइलर के आंतरिक को देख सकते हैं जो आंतरिक कक्षा है जो फ़ंक्शन अभिव्यक्तियों के लिए बाइटकोड उत्पन्न करती है।

क्लोजर के संबंध में उससे कहीं अधिक चल रहा है, जैसे कि यह नामस्थान और प्रतीक इंटर्निंग को नियंत्रित करता है। मुझे पूरी तरह से यकीन नहीं है कि यह इस तथ्य के आसपास कैसे हो जाता है कि मानक क्लासलोडर उस वर्ग के नए संस्करण के साथ एक लिंक्ड क्लास को प्रतिस्थापित नहीं करेगा, लेकिन मुझे संदेह है कि कक्षाओं का नाम कैसे बनाया जाता है और प्रतीकों को कैसे प्रशिक्षित किया जाता है। क्लोजर अपने स्वयं के क्लासलोडर को भी परिभाषित करता है, एक निश्चित clojure.lang.DynamicClassLoader, जो java.net.URLClassLoader से प्राप्त होता है, ताकि उसके साथ कुछ करने के लिए हो; मुझे यकीन नहीं है।

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

आशा है कि यह इस विषय पर थोड़ा और प्रकाश डालेगा।

2
सभी कि आवश्यक है

है:

  • भाषा चाहिए
  • एक अमूर्त समारोह/विधि कॉल (वार्स या परिवर्तनशील-नामस्थान)
रीडायरेक्ट करने के लिए नए कोड (eval) लोड करने की क्षमता है
1

हां, यह अन्य भाषाओं में भी संभव है। मैंने इसे ऑनलाइन सर्वर के लिए पायथन में किया है।

कुंजी विशेषता को रनटाइम पर नए कार्यों और विधियों को परिभाषित करने या परिभाषित करने की क्षमता है और यह पाइथन के साथ आसान है जहां आपके पास "eval", "exec" है और जहां कक्षाएं और मॉड्यूल प्रथम श्रेणी की वस्तुएं हो सकती हैं रनटाइम पर पैच किया।

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

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

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

आईएमओ इस प्रकार की लाइव हैकिंग करने का बुरा हिस्सा यह है कि एक बार जब आप चल रहे इंस्टेंस को ठीक कर लेंगे और आप सुनिश्चित कर सकते हैं कि फिक्स काम करता है, तो आपको अभी भी नियमित स्रोत कोड में करना होगा और यदि फिक्स ' टी तुच्छ आप 100% सुनिश्चित नहीं कर सकते हैं कि सर्वर के एक अद्यतन संस्करण को बूट करने के बाद फिक्स काम करेगा।

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

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