होमोआइकॉनिक (Homoiconic) Python भाषा
(substack.com/aljamal)- Lisp की होमोआइकॉनिसिटी वह गुण है जिसमें code और data को एक ही रूप में संभाला जाता है, और क्लासिक “Lisp in Lisp” को Python में लाने पर यह idea ज़्यादा परिचित syntax के भीतर सामने आता है
- मूल Lisp में code representation M-expression और data representation S-expression दोनों थे, और “Lisp in Lisp” ने M-expression में S-expression Lisp को implement किया
- Python version में S-expression को Python lists के रूप में दर्शाया गया है, और M-expression को function calls और conditionals में बदलकर अलग parser के बिना interpreter बनाया गया है
- पहला interpreter
atom,eq,car,cdr,cons,appendजैसे list primitives पर आधारित है, औरlambdasupport के लिएassoc,pairlis, और environment list जोड़े गए हैं evalexpression और environment को साथ में लेकर variable binding संभालता है, औरpairlisवassocके जरिए dynamic scope तरीके से arguments और values को जोड़ता है
Lisp ने code और data का एकीकरण दिखाया
- Lisp ने 1960 के दशक की शुरुआत में John McCarthy के Lisp paper और Lisp 1.5 manual के जरिए कई ऐसे ideas छोड़े जो दशकों बाद भी प्रासंगिक हैं
- इनमें मुख्य है होमोआइकॉनिसिटी
- सामान्य languages में code को data पर काम करने वाले operations की श्रृंखला के रूप में समझा जाता है
- Lisp code और data को एक ही रूप में संभालता है, जिससे operator और operand की सीमा धुंधली हो जाती है
- Alan Kay ने Lisp 1.5 manual के पेज 13 के निचले हिस्से में मौजूद “Lisp in Lisp” code को “Maxwell's Equations of Software” कहा था
- इसमें यह उद्धरण शामिल है कि कुछ lines of code में पूरी programming दुनिया समाई हुई है, इसलिए यह एक बड़ा बोध था
“Lisp in Lisp” को Python में लाने का तरीका
- लक्ष्य क्लासिक “Lisp in Lisp” code को Python में फिर से लिखते हुए original code की spirit को यथासंभव बनाए रखना है
- Lisp में दो तरह के syntactic representations होते हैं
- M-expression: code representation, meta का संक्षेप
- S-expression: data representation, symbolic का संक्षेप
- दोनों representations semantically equivalent हैं
- मौजूदा “Lisp in Lisp” code M-expression में लिखा गया था और S-expression Lisp को implement करता है
- Python implementation में Lisp S-expression को Python list के रूप में represent किया गया है
- Lisp “List Processing” का संक्षेप है, और list नाम की एक data structure को केंद्र में रखकर काम करता है
- Python list को Lisp S-expression को emulate करने के लिए उपयुक्त representation के रूप में इस्तेमाल किया गया है
- M-expression को Python के function calls और conditionals जैसे code structures में translate किया गया है
- इस mapping की वजह से string manipulation या अलग parser implement किए बिना interpreter बनाया जा सकता है
list primitives से बना पहला interpreter
- Lisp implementation के लिए language के बाहर से उपलब्ध कुछ basic functions की जरूरत होती है
- Python implementation में इस्तेमाल हुए list primitives ये हैं
atom(x): जाँचता है किxlist है या नहींeq(x,y): जाँचता है किxऔरyसमान हैं या नहींcar(x): list का पहला elementcdr(x): list का बाकी हिस्साcons(x,y): atom को list से जोड़ता हैappend(x,y): दो lists को जोड़ता है
- कुछ recursive primitives को छोड़कर, Llama3-70b को Groq पर इस्तेमाल करके “Lisp in Lisp” code के subset को चलाने वाला interpreter जल्दी बनाया जा सका
- उदाहरण में Python list S-expression की तरह काम करती है
- पूरा code github gists पर उपलब्ध है
lambda और recursion के लिए विस्तार
- पहली implementation में अहम feature
lambdaनहीं थाlambdaLisp में anonymous functions define और call करने का मुख्य तरीका है- Lisp में
lambdaन हो तो recursion implement नहीं किया जा सकता - recursion न हो तो हर computable चीज़ को compute कर पाने की न्यूनतम कसौटी, यानी Turing completeness, तक नहीं पहुँचा जा सकता
lambdaजोड़ने के लिएassoc(x,y)औरpairlis(x,y)जोड़े गएassoc(x,y)list से implement किया गया key/value lookup है, और association list का इस्तेमाल करता हैpairlis(x,y)Python केzip(x,y)की तरह दो lists को जोड़े में बाँधता है
- मूल Lisp में साधारण linear scan भी recursion से ही संभालना पड़ता था
- क्योंकि मूल Lisp में loops नहीं थे
- Python translation में
assocऔरpairlisको list comprehensions से अधिक संक्षेप में व्यक्त किया जा सकता है
CONDprocessing में मूल Lisp केevconको loop में translate किया गया है, औरLAMBDAprocessing में भीevlisपर वही तरीका लागू किया गया है
environment list और dynamic scope
- मूल Lisp का
evalfunction दो arguments लेता है- पहला argument evaluate किया जाने वाला S-expression है
- दूसरा argument key/value list के रूप में environment list है
- environment
LAMBDAprocessing में variable binding को बनाए रखता है- अगर function में
xvariable है और data assign किया जाता है, तोpairlisxsymbol और data को pair करता है - paired value environment list में store या add की जाती है
- जब
xकी जरूरत होती है, तोassocउसे environment में खोजकर expression में फिर से substitute करता है
- अगर function में
- इस binding तरीके को dynamic scope कहा जाता है
- अंतिम implementation मूल “Lisp in Lisp” को Python में लाने का रूप है, और आखिरी example में
lambdaexecution तक शामिल है
2 टिप्पणियां
Hacker News की राय
अगर आपकी रुचि सरल और तेज़ Python-स्टाइल scripting Lisp में है, तो Clojure-स्टाइल भाषाओं Hy और Janet को देखना चाहिए
यह जल्दी शुरू करने के लिए binary के रूप में मिलता है, और रोज़मर्रा के कामों के लिए libraries भी शामिल हैं
CL↔Python पक्ष के लिए https://github.com/CodyReichert/awesome-cl?tab=readme-ov-fil... भी देखने लायक है
मैं Hy maintainer हूँ, और अगर कोई meaningful performance difference दिखे, तो वह bug है
M-expressions इस जाल से बचती हैं, इसलिए मैं सोचता हूँ कि क्या कभी कोई वास्तविक भाषा रही है जो Lisp की homoiconicity और conceptual elegance को बनाए रखते हुए ऐसी parentheses की मांग न करे
यह ठीक वही है जो आप सोच रहे हैं। इसका विवरण भी बिल्कुल सीधा है: “Program in YAML”
इसके बाद कहने को लगभग कुछ नहीं बचता
ज़रा ठहरिए, क्या यह बस Python में implement किया गया Lisp नहीं है?
जैसा शीर्षक संकेत देता है, यह homoiconic Python तो नहीं लगता; क्या मैं कुछ मिस कर रहा हूँ?
इसके लिए Python syntax को tuples, lists और dictionaries से बना होना चाहिए, और interpreter को उन्हें सीधे evaluate करना होगा
एक और functional language जिसे Python में संक्षेप में implement किया जा सकता है, वह है Binary Lambda Calculus
कोड का बड़ा हिस्सा BLC के pure I/O model को संभालता है, और variable lookup के लिए associative list की जगह environment array को de Bruijn indices से index किया जाता है
उसी पेज पर 9 अन्य language implementations भी हैं, और BLC का self-interpreter parser और tokenizer सहित 232 bits (29 bytes) में सबसे संक्षिप्त है
[1] https://rosettacode.org/wiki/Universal_Lambda_Machine#Python
MIT के introductory programming course में सभी छात्रों से Python में Lisp interpreter लिखवाया जाता है
यह उस दौर की एक निशानी है जब वह course सच में Lisp में पढ़ाया जाता था
https://py.mit.edu/spring24
मैंने कुछ मिलता-जुलता JS lists के साथ किया था: https://github.com/andrelaszlo/js-lisp
यह मज़ेदार भी है और विडंबनापूर्ण भी कि आधुनिक भाषाएँ कई दशकों बाद Lisp की शानदार खूबियों को फिर से खोज रही हैं
कुछ दिन पहले मेरा एक Python प्रोग्राम, जो 9 घंटे से मेरे होम सर्वर API को कॉल कर रहा था, रुक गया और मैं बहुत खीझ गया। वह मिलते-जुलते API कॉल कर रहा था और predefined prompt templates तथा grammar constraints के साथ LLM को कॉल कर रहा था
मैं बची हुई iterations को अलग से चलाने से पहले प्रोग्राम की state सेव करके उसे बंद करना चाहता था, लेकिन running Python code को modify करने या variables को inspect करने का कोई तरीका नहीं मिला, और आखिरकार 9 घंटे का काम खो दिया
कुछ दिनों बाद मैंने https://malisper.me/debugging-lisp-part-1-recompilation/ देखा, और यह जानकर हैरानी हुई कि Common Lisp में ऐसी सुविधा दशकों पहले से भाषा में built-in थी। सबसे शक्तिशाली macro system जैसी दूसरी खूबियों के साथ भी यही बात है
लेकिन यही समस्या भी है। अभिव्यक्ति की शक्ति बहुत ज़्यादा है
Lisp मुझे हमेशा mixed-media visual art की याद दिलाती है; अभिव्यक्ति की आज़ादी अच्छी लगती है, लेकिन आखिर में उसका परिणाम अक्सर अधिक पारंपरिक single-medium art की तुलना में कमतर होता है। इससे पता चलता है कि माध्यम की सीमाएँ भी अभिव्यक्तिशीलता जितनी ही महत्वपूर्ण हैं
या https://github.com/malor/cpython-lldb भी है
और अधिक तरीकों के लिए https://github.com/albertz/pydbattach/
यह implementation homoiconic Python कैसे है?
क्या ऐसा कोई Lisp है जिसमें type system हो, जो प्रोग्राम के बड़े होने पर Lisp के पढ़ने में कठिन हो जाने की प्रवृत्ति को कुछ हद तक नियंत्रित कर सके?
अगर type system नहीं भी हो, तो क्या कोई और तत्व है जो इस प्रवृत्ति को संभाल सके, यह जानने की जिज्ञासा है
metaprogramming सचमुच शानदार है, लेकिन कभी-कभी यह अब तक देखे गए सबसे कठिन abstract Haskell जैसा पढ़ाई देता है, बस उसमें राह दिखाने वाले type signatures भी नहीं होते
मेरे हिसाब से type system और linter कोड को अपने-आप काबू में रखने के सबसे अच्छे औज़ार हैं, लेकिन Lisp चुनने की वजह को ही सीमित किए बिना Lisp projects की इस प्रवृत्ति को कैसे रोका जा सकता है, यह समझना मुश्किल है
व्यवहार में standard के सबसे करीब SBCL compile-time type checking भी काफ़ी अच्छी देता है
उदाहरण के लिए,
(declare (type String a b))के बाद अगर(+ a b)लिखें, तो यह warning दे देता है किAका derived typeSTRINGहै जबकि उसेNUMBERहोना चाहिएScheme से CL में आने की मेरी सबसे बड़ी वजह यही type checking थी, और उसके बाद restarts तथा continuable asserts जैसी छोटी लेकिन उपयोगी सुविधाओं के कारण मैं वहीं बना रहा
अगर expression-oriented Python होता, तो वह मौजूदा Python से कहीं बेहतर होता
यह लेख शीर्षक से जो संकेत मिलता है, उसके बारे में नहीं है, लेकिन Lisp को समझाने के लिए अच्छा है
फिर भी यह ठीक है, लेकिन “expressions को second-class syntax की तरह रखना” काफ़ी बुनियादी और जानबूझकर किया गया चुनाव है। यह इस तरह भी डिज़ाइन किया गया है कि जो developer parentheses soup लिखना चाहता है, उसके लिए काम आसान न हो
इसलिए
lambda:और:=को जानबूझकर थोड़ा भद्दा रखा गया है ताकि उनका इस्तेमाल कम हो, और anonymous functions भी नहीं लिखे जा सकतेPython ऐसी भाषा है जो कहती है कि अगर आपको कुछ चतुर करना है, तो callable object की बजाय iterator के रूप में करें
काश हर चीज़ में Lisp-शैली की बिना-व्याकरण वाली संरचना होती
मेरे लिए वह बेहतर है, और शुक्र है कि CL में ऐसा करना आसान है
Python में एम्बेड किया गया Lisp का एक dialect
https://hylang.org/