Elixir की ओर बदलाव
(leemeichin.com)- एक Ruby backend डेवलपर ऐसी कंपनी में शामिल होने के बाद, जहाँ Elixir मुख्य भाषा है, Ruby से प्रभावित syntax की वजह से Elixir/Erlang का अनुभव न होने पर भी अपेक्षाकृत जल्दी ढल गया
- Elixir, Ruby की तरह syntax का आनंद और DSL-अनुकूलता देता है, लेकिन class, instance और inheritance के बिना immutable functional model के ज़रिये state management का बोझ कम करता है
- जहाँ Ruby DSL runtime में object और method को dynamically define करने पर निर्भर करता है, वहीं Elixir DSL compile-time macros से runtime code generate करता है, जिससे errors पहले सामने आ जाते हैं
- pipeline syntax,
withexpression, और{:ok, result}जैसे Result tuples normal flow और recoverable error handling को अलग करने में उपयोगी हैं - Ruby में exceptions,
nil/false, और object के internal error state पर निर्भर रहने की तुलना में Elixir का explicitok/errorreturn ज़्यादा संतोषजनक लगता है, और 3 महीने इस्तेमाल करने के बाद भी लिखने का अनुभव आनंददायक बना हुआ है
Ruby अनुभव वाले डेवलपर ने Elixir के साथ कैसे तालमेल बिठाया
- नई नौकरी में backend की मुख्य भाषा Elixir है, और शुरू करने से पहले Elixir या Erlang code लिखने का कोई अनुभव नहीं था
- Prolog को थोड़ा इस्तेमाल करने के अनुभव की वजह से Erlang पूरी तरह अपरिचित नहीं लगा, और Elixir को Ruby से गहरे प्रभावित syntax की वजह से जल्दी सीखा जा सका
- अभी best practices, architecture, और Erlang के low-level concepts में विशेषज्ञता नहीं है, लेकिन इससे Elixir में प्रवेश करने में बाधा नहीं आई
- Elixir को ऐसी भाषा के रूप में देखा गया जो Ruby वाली programming की खुशी बनाए रखती है, लेकिन OOP भाषाओं में आम state-संबंधी pitfalls को कम करती है
- class, instance, और inheritance नहीं हैं
- यह immutability और functional style पर आधारित है
- यह static type system से बंधी नहीं है
- mutable state वाला code maintain करना कहीं अधिक कठिन होता है, और यह हमेशा साफ़ नहीं होता कि state function-local है, instance-level है या class-level
- Ruby में सब कुछ object होता है, इसलिए हर object के पास state हो सकता है, और सहज DSL बनाने की प्रक्रिया में function-, instance-, और class-level state का मिश्रण आसानी से हो जाता है
- Ruby की Eigenclass को class की class जैसा समझा जा सकता है, और एक instance, eigenclass को modify करके दूसरे instances की state को भी प्रभावित कर सकता है
DSL और compile-time macros का अंतर
- Elixir भी Ruby की तरह DSL को support करता है और syntax भी परिचित लगता है, लेकिन मूल अंतर DSL के बनने के समय में है
- Elixir DSL, compile-time macros हैं, जो runtime code generate करते हैं
- Ruby DSL runtime को modify करके object और method को dynamically define करने के तरीके पर निर्भर करता है
- Elixir macros का गलत उपयोग करने पर library में errors आ सकते हैं, और generated macro code गलत होने पर compiler error होता है
- क्योंकि ये errors compile time पर सामने आ जाते हैं, testing को असली application logic पर ज़्यादा केंद्रित किया जा सकता है
pipeline और with से बनने वाला flow
- Elixir का pipeline operator
|>उपयोगी है, लेकिन Clojure के threading operators के भी अपने फायदे हैं- Clojure का
->, Elixir के|>की तरह पिछले expression के result को अगली function के पहले argument में डालता है - Clojure का
->>पिछले result को अगली function के आख़िरी argument में डालता है - यह फ़र्क छोटा लग सकता है, लेकिन anonymous functions का उपयोग किए बिना interop code लिखने में उपयोगी है
- Clojure का
- Elixir का
withexpression, Haskell और Lisp केletकी याद दिलाता है, लेकिन ऐसा लगता है जैसे उसमेंotherwisebuilt-in हो- इससे जटिल functions को ऊपर के normal path और नीचे के error handling हिस्से में बाँटा जा सकता है
- अगर
{:ok, result}पर pattern matching न हो पाए, तो उसे recoverable error की तरह संभाला जा सकता है, इसलिए यह Result tuples के साथ अच्छी तरह मेल खाता है
explicit error returns के फायदे
- Ruby में control flow के लिए exceptions का उपयोग आम है, और explicit error types की जगह अक्सर success result,
nil,false, या object के internal error state पर निर्भर किया जाता है- उदाहरण के लिए
model.update(params)असफल होने परfalseलौटाता है और असफलता का कारणmodel.errorsमें रखता है - यह फिर से mutable state की समस्या से जुड़ता है
- उदाहरण के लिए
Result/Eithermonad की तरहok/errorयाleft/rightको return value में साफ़-साफ़ दिखाने का तरीका, Ruby-शैली exception flow की तुलना में अधिक संतोषजनक लगता है- 3 महीने तक Elixir का उपयोग करने के बाद भी, Elixir ऐसी भाषा बनी हुई है जो Ruby के “programming is fun” वाले एहसास को आगे बढ़ाती है
1 टिप्पणियां
Hacker News की राय
पिछले कुछ हफ्तों से लगभग हर दिन Elixir YouTube वीडियो देख रहा हूँ
लगता है कुछ समय पहले Elixir कॉन्फ्रेंस हुई थी, और कुछ वीडियो देखने के बाद YouTube लगातार Elixir कंटेंट रिकमेंड कर रहा है
Erlang के आइडियाज़ मुझे पसंद हैं, लेकिन पहले जब ejabberd चैट सर्वर जैसी चीज़ों के साथ काम किया था, तब यह बहुत अजीब लगा था
जब भी Joe Armstrong के वीडियो देखता हूँ, लगता है कि वे कम आंके गए जीनियस थे, और isolated processes तथा message passing, यानी actor model, distributed programming के भविष्य के लिए एक अच्छा उम्मीदवार लगता है
Crockford की नई Misty language के वीडियो https://www.youtube.com/watch?v=R2idkNdKqpQ में भी सवाल Elixir/Erlang की तरफ चले गए, यह बात प्रभावशाली लगी
लेकिन LiveView और LiveBook के “magic” को देखकर अभी भी 100% भरोसा नहीं होता। जो चीज़ें बहुत अच्छी लगती हैं, उनमें अक्सर कोई न कोई पकड़ होती है, और GenServer जैसे अनजाने शब्द तथा platform-specific knowledge ऐसा महसूस कराते हैं जैसे एक one-way portal में प्रवेश कर रहे हों, इसलिए पता नहीं यह ज्ञान कहीं और transferable है या नहीं
जब Go के पास भी concurrency की अच्छी कहानी है, तब छोटे community वाली language और platform में hiring, libraries और resources के लिहाज़ से बड़ा risk होता है। फिर भी Elixir community ने जो हासिल किया है वह सचमुच प्रभावशाली है, और type system का implementation कैसे होगा यह देखने की उत्सुकता है
sendकैसे काम करता हैGenServer उस pattern का स्वाभाविक generalization है जिसे आप सीधे बहुत बार लिखेंगे, और actor knowledge दूसरी जगह भी काम आ सकती है
Go में भी actor abstraction लागू करने वाली libraries हैं, और process mailbox को Go के channels की तरह message queue मान सकते हैं। फ़र्क ज़्यादा इस बात में है कि interpreter और processor yielding कैसे काम करते हैं
दूसरी तरफ LiveView का magic जैसा लगना सही है। इसमें सचमुच बहुत सारे moving parts साथ काम करते हैं, और यह कई communities में पिछले 10 सालों में बने काम का नतीजा है, इसलिए abstractions भले बहुत हों, लेकिन underlying ideas mature हैं
Riak जैसी चीज़ें अपने समय से आगे थीं, और उनमें लगभग यह विचार मौजूद था कि GenServer बनाते हुए जैसे robust distributed systems भी बनाए जा सकते हैं
Chris और José दोनों शुरुआती Elixir दायरे के बाहर visibility बढ़ाने के लिए बहुत कोशिश कर रहे हैं, और community के लोग भी अपने-अपने स्तर पर वही कर रहे हैं
hiring उतनी जटिल नहीं है जितनी लगती है। बहुत से लोग इस language में काम करना चाहते हैं, और अच्छे developers काफ़ी जल्दी गति पकड़ लेते हैं
फिर भी niche language होने की बात को साधना पड़ता है। language के लिए यह risk है, लेकिन short term में niche tech में skilled होने का फ़ायदा भी दे सकता है
ज़्यादातर चीज़ें देखकर समझी जा सकती हैं कि वे क्या कर रही हैं, और code पढ़ा जा सकता है। JavaScript में अक्सर ऐसा नहीं होता
hiring और tutorials ढूँढना मुश्किल है, यह बात सही है। जैसे Elixir में जो काम मैं करना चाहता हूँ, वह पहले से कर चुके किसी व्यक्ति को ढूँढना JavaScript की तुलना में कहीं कठिन है
libraries कोई बड़ी समस्या नहीं रहीं, और काफ़ी हैं। API के साथ काम करते समय कभी JavaScript या Python के client libraries होते हैं लेकिन Elixir के नहीं, लेकिन आमतौर पर सिर्फ HTTP client से API इस्तेमाल करना इतना मुश्किल नहीं होता
अभी Node.js इसलिए इस्तेमाल कर रहा हूँ क्योंकि करना पड़ रहा है, न कि इसलिए कि चाहता हूँ। इसे इस्तेमाल करने के बाद लगता है कि मैं कभी भी नया project Node.js से शुरू नहीं करूँगा
Node.js का concurrency control, scalability, और reliability/observability tooling, BEAM ecosystem से काफ़ी पीछे है
मेरा मानना है कि लोगों को Elixir इसलिए अविश्वसनीय रूप से पसंद आता है क्योंकि वे उन platforms के इतने आदी हो चुके होते हैं जिन्हें resilience के लिए डिज़ाइन नहीं किया गया था
LiveBook में, diff calculation या data compression की सटीक details को छोड़ दें, तो सही mental model बनाना काफ़ी आसान है। बहुत ज़रूरत पड़े तो WebSocket messages देखकर भी काफ़ी समझा जा सकता है कि क्या हो रहा है
LiveView हैरान करने वाली हद तक intuitive है
Elixir/Erlang का बड़ा आकर्षण यह है कि web server context में background jobs चलाना वास्तव में बहुत आसान है, और blocking I/O से पूरे server के रुक जाने की चिंता नहीं रहती
पिछली नौकरी में webhook handler के अंदर बहुत से HTTP requests करने पड़ते थे, और जब वे एक साथ काफ़ी संख्या में आते थे तो सारे OS processes व्यस्त हो जाते थे और पूरी site ठप हो जाती थी
ऐसे समय में कई बार बहुत ज़ोर से लगा कि काश यह Elixir होता, तो बस एक Task शुरू करके आगे बढ़ जाते
Tokio async processes के बीच objects share करने का विचार, Elixir की first-class capabilities की तुलना में ज़्यादा कठिन लगा। यह असंभव है या मूल रूप से कठिन है, ऐसा नहीं कह रहा, लेकिन इसे सही तरीके से बनाना ज़्यादा मुश्किल लगा
मैं background jobs को HTTP requests से अलग compute capacity पर रखना चाहूँगा। resource usage patterns अलग होते हैं, और retries, error handling, तथा backpressure को साफ़ रखने के लिए state या queue को आगे रखना चाहता हूँ
बेशक अच्छा होता अगर यह जटिलता न होती और वही framework सब संभाल लेता, लेकिन सिर्फ programming language या runtime के दम पर वहाँ तक पहुँचना मुश्किल लगता है। monitoring और operational procedures भी समाधान का हिस्सा होने चाहिए
Sasa Juric की The Soul of Erlang and Elixir talk अच्छी तरह दिखाती है कि यह language कितनी शक्तिशाली हो सकती है
https://www.youtube.com/watch?v=JvBT4XBdoUE&t=4
जो लोग “let it crash” या “BEAM concurrency को सही ढंग से करता है” जैसी बातें सुन चुके हैं लेकिन अभी तक बात ठीक से समझ नहीं पाए हैं, उन्हें मैं इसे ज़ोरदार सिफ़ारिश करूँगा। यह सचमुच शानदार talk है
1991 से मैं हर दिन Erlang में प्रोग्रामिंग करता आया हूँ, और आगे भी ऐसा ही करने का इरादा था
इस दौरान Haskell, Rust, Elixir वगैरह भी आज़माए। strong types बहुत मूल्यवान हैं, लेकिन आखिर में बात इस पर आकर टिकती है कि सबसे ज़्यादा मज़ा कहाँ आता है
Erlang वह प्रोग्रामिंग भाषा है जिसमें मुझे सबसे ज़्यादा आनंद मिलता है, और कुछ साल बाद रिटायर होने तक कम-से-कम Erlang को हैक करता रहने की योजना है
मुझे Elixir के बारे में जाने हुए एक साल से थोड़ा ज़्यादा हुआ है, और इसे सीखने व अलग-अलग components में लगाने की प्रक्रिया काफ़ी आनंददायक रही
सबसे बड़ा आनंद इस बात से आता है कि सिर्फ़ BEAM/OTP के साथ ही बहुत कुछ built-in मिलता है
जिस टीम के साथ मैं काम करता था, उसमें किसी ने एक बार कहा था, “BEAM/OTP, complex हिस्सों के बिना k8s जैसा है”
कई साल Elixir सीखा, लेकिन हाल में मैंने जो OpenShift, यानी k8s migration डिज़ाइन किया, उसकी वजह से यह फ़ायदा कुछ हद तक कमज़ोर पड़ गया
अगर k8s न होता तो यह कहीं ज़्यादा आकर्षक प्रस्ताव होता। उसकी जगह हमने मौजूदा development stack बनाए रखा, और जो काम OTP से होता, उसे k8s concepts से हासिल किया। बेशक, trade-offs अलग थे
“Elixir आपको Ruby जैसी मज़ेदार भाषा देता है, लेकिन object-oriented भाषाओं की state से जुड़ी फँसाने वाली समस्याएँ हटा देता है। इसमें classes नहीं हैं, instances नहीं हैं, inheritance नहीं है, यह immutable/functional है, और static type system की जकड़न में नहीं बंधा”
मैं यह तो चाहता हूँ, लेकिन काश इसमें types भी होते। बड़े codebase में strong types सही होते हैं, इस बात पर मुझे पूरा भरोसा है। वे बहुत-सी magic चीज़ों को सामने ले आते हैं और reasoning आसान बनाते हैं
functions की pattern matching/unification की वजह से डेटा के इधर-उधर जाने पर उसका shape क्या है, इसका काफ़ी अच्छा अंदाज़ा रहता है। फिर भी असहज लग रहा हो तो Dialyzer है
Dialyzer, Erlang/Elixir के लिए एक static analysis tool है। यह standard Erlang release में शामिल होता है, और “DIscrepancy AnaLYZer for ERlang programs” का संक्षेप है
यह एक अकेले Erlang module या application bundle में type errors, dead code, और unnecessary tests जैसी inconsistencies पकड़ता है। यह सही प्रोग्राम के आधार पर types infer करता है, इसलिए type annotations अनिवार्य नहीं हैं, लेकिन हों तो बेहतर warnings देता है
https://fly.io/phoenix-files/adding-dialyzer-without-the-pai...
https://elixir-lang.org/blog/2023/06/22/type-system-updates-...
GenServer objects के काफ़ी क़रीब हैं, और वे सिर्फ़ associated functions वाले structs नहीं हैं, बल्कि messages के ज़रिए interact करते हैं
अच्छा होता अगर प्रोजेक्ट के GenServer के लिए common registration, memory settings, logging जैसी बुनियादी template रखी जा सकती, और फिर उसे तीन variants के खास काम के हिसाब से specialize किया जा सकता
Elixir इस समस्या को macro code generation से भरने की कोशिश करता है, लेकिन वह बहुत अच्छा नहीं है। मेरे GenServer मामूली दोहराव से भरे हुए हैं
“Ruby में control flow के लिए exceptions का इस्तेमाल आम है” — मुझे यह बात बस ग़लत लगती है
उस पैराग्राफ़ के बाद दिया गया उदाहरण Rails के
updatemethod का है, लेकिन standard Rails examples और generators में जो तरीका इस्तेमाल होता है, वह exceptions का उपयोग न करने वालाupdateversion हैकुछ पैमाने के बाद libraries या applications इतने जटिल हो जाते हैं कि वे descriptive error classes की ओर बढ़ते हैं। ActiveRecord जैसी state-based error handling तकलीफ़देह होती है
अब Ruby में कुछ हद तक pattern matching भी है, इसलिए मुझे लगता है कि यह control flow exceptions की जगह ले सकता है। exception throw करने की बजाय class खुद return की जा सकती है और उस पर match किया जा सकता है
बहुत-से gems control flow के लिए exceptions का इस्तेमाल करते हैं, और जिन apps व libraries को मैंने लिखा या maintain किया है, उनमें भी यह आम था
Rails में बहुत-से
bang!methods हैं जो error होने पर exception throw करते हैं। लेकिन आम तौर पर Ruby में, Python की तरह, control flow के लिए exceptions का इस्तेमाल वास्तव में काफ़ी आम हैमैं F# से आया हूँ और Elixir को बस थोड़ा-सा ही इस्तेमाल किया है, लेकिन दुर्भाग्य से यह भाषा थोड़ी उलझी हुई लगती है
उदाहरण के लिए, function signature में सिर्फ नाम और parameters की संख्या दिखती है, type नहीं, इसलिए बड़ी libraries में क्या कहाँ जा रहा है यह दिमाग में ट्रैक करना काफी मुश्किल हो जाता है
यह भी अक्सर तब तक पता नहीं चलता कि गलत parameter दिया गया है, जब तक code चलाकर debug न कर लिया जाए। F# जैसी strict type languages में ऐसी समस्याएँ coding के दौरान ही पकड़ ली जाती हैं, जिससे काफी समय और मेहनत बचती है
इसलिए Erlang ecosystem के अलावा Elixir की मुख्य appeal क्या है, यह जानने की जिज्ञासा है
https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...
अभी यह वाकई एक dynamic language है, इसलिए language से type information बहुत ज़्यादा नहीं मिलती। यह अभी static type language नहीं है
लेकिन जिन दूसरी languages का मैंने इस्तेमाल किया है, उनके मुकाबले Elixir को बढ़त देने वाली चीज़ वास्तव में BEAM/OTP है
मेरे अनुभव में, दूसरी languages में जिन कई features को library के रूप में लाना पड़ता, वे यहाँ built-in मिलते हैं। ऐसी libraries complexity बहुत बढ़ाती हैं और कभी-कभी learning curve भी बड़ा कर देती हैं। async से जुड़े features इसका अच्छा उदाहरण हैं
कुछ समय Elixir इस्तेमाल करने और इन built-in tools व abstractions को सीखने के बाद, मुझे लगता है कि पूरे ecosystem की ताकत समझ में आने लगती है
मुख्य appeal शायद यह है कि यह Ruby-जैसे syntax के साथ BEAM पर चलता है। मेरे हिसाब से BEAM अपने आप में काफी बड़ा फायदा है
यह F# के types से तुलना नहीं करता, लेकिन typespec का इस्तेमाल किया जा सकता है: https://hexdocs.pm/elixir/1.15.7/typespecs.html
उदाहरण के लिए, Axon, Nx और BumbleBee के साथ machine learning का एक ऐसा flow है जो native integration और GPU compilation तक सपोर्ट करता है: https://www.youtube.com/watch?v=HK38-HIK6NA
community वास्तव में इस दिशा को काफी मज़बूती से आगे बढ़ा रही है
LiveView जिस तरह काम करता है, वह भी आकर्षक है। हालाँकि .NET में Blazor है
Elixir को embedded के लिए Nerves, scripting और कई अलग contexts में इस्तेमाल किया जा सकता है। जहाँ तक मुझे पता है, F# भी scripting के लिए इस्तेमाल हो सकता है
कुल मिलाकर, BEAM और उससे जुड़ी architectures इसकी ताकत हैं, और package manager Hex भी वाकई बहुत अच्छा है। बेशक .NET के tools भी अच्छे हैं
typespec, pattern matching और guards से काम चलाया जा सकता है, लेकिन यह काफी झंझट वाला है, और कम-से-कम community-driven JetBrains plugin में यह IDE/IntelliSense-friendly नहीं है
इसे शुरुआत में नहीं, बल्कि 2 साल इस्तेमाल करने के बाद लिखना बेहतर होगा
नई चीज़ें शुरू में हमेशा बिना किसी कमी वाली चमकदार universal solution जैसी लगती हैं
first impressions की भी अपनी जगह होती है, और मैं वही दे रहा हूँ। क्योंकि target audience Elixir/Erlang experts नहीं, बल्कि Ruby experts हैं
Elixir तब भी और अब भी एक gem जैसा है। यह perfect नहीं है, लेकिन हैरान करने वाली तरह से अच्छी तरह टिकता है
मैं लगभग 2007 से Ruby इस्तेमाल कर रहा हूँ
जब भी मैंने अगली “cool” चीज़ पर जाने की कोशिश की, आखिर में Ruby पर ही वापस आया, क्योंकि मुझे Ruby पसंद है, Ruby में मैं सबसे तेज़ काम कर पाता हूँ, और वही काफी था
लेकिन हाल में Elixir की चर्चा बहुत ज़्यादा दिख रही है, और इसका look and feel मुझे पसंद आ रहा है। मैं जल्द ही एक नया side project शुरू करने वाला हूँ, इसलिए शायद इसे एक बार आज़माना चाहिए