1 पॉइंट द्वारा GN⁺ 2 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Mercury लगभग 20 लाख लाइनों के Haskell codebase के साथ 3 लाख से अधिक कंपनियों को बैंकिंग सेवाएं देता है, और 2025 में 248 अरब डॉलर के ट्रांजैक्शन व 65 करोड़ डॉलर की annualized revenue संभालता है
  • Mercury में Haskell का मूल्य शुद्धता पर नहीं, बल्कि ऑपरेशनल ज्ञान को API और types में समेटने, जोखिमभरे व्यवहार को संकीर्ण सीमाओं के पीछे रखने, और सुरक्षित रास्ते को आसान रास्ता बनाने में है
  • विश्वसनीयता का मतलब सभी विफलताओं को रोकना नहीं, बल्कि सिस्टम की परिवर्तनशीलता को absorb करने की क्षमता है; type system error classes को बाहर रखता है और संस्थागत ज्ञान को ऐसे दस्तावेज़ की तरह छोड़ता है जिसे compiler लागू कर सके
  • Mercury वित्तीय workflow में retry, timeout, cancellation और crash recovery के लिए Temporal को durable execution framework की तरह इस्तेमाल करता है, और Haskell SDK hs-temporal-sdk को open source के रूप में जारी करता है
  • प्रोडक्शन में Haskell की असली उपयोगिता हर चीज़ को types में ठूंस देने में नहीं है; बल्कि data loss, वित्तीय त्रुटि और regulatory issues तक ले जाने वाले invariants को types से सुरक्षित रखते हुए, complexity को encapsulate करना और उसे testing, documentation व code review के साथ चलाना है

Mercury में Haskell का ऑपरेशनल स्केल और विश्वसनीयता का दृष्टिकोण

  • Mercury comments आदि को छोड़कर लगभग 20 लाख लाइनों का Haskell codebase चलाता है
  • Mercury 3 लाख से अधिक कंपनियों को बैंकिंग सेवाएं देने वाली एक fintech कंपनी है, और 2025 में 248 अरब डॉलर के ट्रांजैक्शन व 65 करोड़ डॉलर की annualized revenue संभालती है
  • कंपनी में लगभग 1,500 कर्मचारी हैं, और engineering संगठन मुख्यतः generalist developers को hire करता है; इनमें से अधिकतर ने joining से पहले Haskell का उपयोग नहीं किया था
  • यह सिस्टम तेज़ growth, SVB संकट के दौरान 5 दिनों में 2 अरब डॉलर की नई deposits के influx, regulatory review, और बड़े वित्तीय सिस्टमों की सामान्य व असामान्य दोनों तरह की स्थितियों से गुजरते हुए कई वर्षों से चल रहा है

विश्वसनीयता विफलता-रोकथाम नहीं, बल्कि बदलाव को absorb करने की क्षमता है

  • पारंपरिक reliability approach विफलताओं की सूची बनाने, checks और tests जोड़ने, और bugs ढूंढने पर केंद्रित रहती है, लेकिन केवल इससे काम नहीं चलता
  • Mercury विश्वसनीयता को सिस्टम की परिवर्तनशीलता को absorb करने की क्षमता के रूप में देखता है
    • सिस्टम को graceful degradation करने में सक्षम होना चाहिए
    • operators सिस्टम को समझ और tune कर सकें, यह ज़रूरी है
    • architecture को सही काम आसान और गलत काम कठिन बनाना चाहिए
  • तेज़ी से बढ़ते संगठन में असली ऑपरेशनल सवाल यह होते हैं कि नया engineer किसी module को पढ़कर समझ सकता है या नहीं, database धीमा होने पर service भी साथ में गिरती है या नहीं, और interface misuse को compiler पकड़ता है या नहीं
  • type system केवल correctness proof नहीं, बल्कि ऑपरेशनल सहायक तंत्र के अधिक करीब है
    • यह कुछ तरह की errors को बाहर कर देता है
    • यह लेखक के जाने के बाद भी संस्थागत ज्ञान को compiler द्वारा पढ़े जा सकने वाले रूप में छोड़ देता है
    • यह wiki की तुलना में अधिक सुसंगत रूप से लागू होने वाला documentation बन जाता है
  • Mercury की stability engineering, product development को धीमा करने वाली quality police नहीं, बल्कि ऐसा collaborative तरीका है जो feature टूटने पर उसके प्रभाव को design की शुरुआत से ही संबोधित करता है
    • failure होने पर blast radius
    • कौन-से कामों में idempotency चाहिए और कैसे
    • rollback का रूप
    • in-progress कामों को कैसे संभाला जाएगा
    • कौन-से सिस्टम विफलता को absorb करते हैं और कौन-से उसे बढ़ा देते हैं, यह पहले से सोचना

शुद्धता भाषा का गुण नहीं, बल्कि interface boundary है

  • Haskell में purity का मतलब यह नहीं कि अंदर कोई side effect नहीं है; बल्कि इसका अर्थ अधिकतर यह है कि interface side effects के रिसाव को रोकने वाली boundary बनाता है
  • bytestring, text, vector जैसी libraries के pure functions के पीछे mutable allocation, buffer writes और unsafe coercion जैसी internal implementations मौजूद होती हैं
  • ST monad computation के भीतर observable in-place mutation और side effects का उपयोग करता है, लेकिन runST का rank-2 type अंदर बनाए गए mutable references को बाहर निकलने से रोकता है
    runST :: (forall s. ST s a) -> a  
    
  • अंदर imperative behavior संभव है, लेकिन बाहर केवल result आता है और mutable state boundary के बाहर leak नहीं होती
  • यह सिद्धांत पूरे ऑपरेशनल सिस्टम पर लागू होता है
    • database layer अंदरूनी तौर पर connection pooling, retries और mutable state का उपयोग कर सकती है
    • cache concurrent mutable map का उपयोग कर सकता है
    • HTTP client में circuit breakers, connection pools और काफी bookkeeping हो सकती है
    • मुख्य बात यह है कि जोखिमभरे behavior को संकीर्ण interfaces में लपेटा जाए ताकि misuse कठिन हो
  • वास्तविक सिस्टमों में लक्ष्य बदलाव से पूरी तरह बचना नहीं, बल्कि यह स्पष्ट करना है कि बदलाव कहां है और codebase में किन लोगों को उसके बारे में जानने की ज़रूरत है

सही काम को आसान काम बनाना

  • बड़े codebase में अक्सर ऐसे pattern बन जाते हैं जहां correctness किसी खास sequence या अदृश्य अतिरिक्त steps पर निर्भर करती है
    • transaction के बाद audit log को flush करना पड़ता है
    • endpoint call से पहले feature flag जांचना पड़ता है
    • notification enqueue को database transaction के अंदर करना पड़ता है
  • अगर यह ऑपरेशनल ज्ञान सिर्फ wiki, onboarding docs, पुराने design reviews, Slack threads, या कुछ senior engineers की याद में ही रहे, तो यह जल्दी गायब हो जाता है
  • Haskell ऐसे procedures को types में encode करके उन्हें भूलना मुश्किल बना सकता है
  • गलत तरीका यह है कि लोगों से सही function इस्तेमाल करने को कहा जाए, लेकिन bypass का रास्ता खुला छोड़ा जाए
    -- Please use this one, not the other one  
    writeWithEvents :: Transaction -> [Event] -> IO ()  
    
    -- Don't use this directly (but we can't stop you)  
    writeTransaction :: Transaction -> IO ()  
    publishEvents :: [Event] -> IO ()  
    
    • बेहतर तरीका यह है कि types को इस तरह दोबारा बनाया जाए कि operation चलाने का एकमात्र रास्ता event publishing को शामिल करे
    data Transact a -- opaque; cannot be run directly  
    record :: Transaction -> Transact ()  
    emit :: Event -> Transact ()  
    
    -- The *only* way to execute a Transact: commit and publish atomically  
    commit :: Transact a -> IO a  
    
  • यहां type system events के बारे में कोई गहरा theorem साबित करने से अधिक, सही ऑपरेशनल प्रक्रिया को सबसे आसान रास्ता बना देता है
  • नया engineer अगर पूछे कि transaction कैसे लिखना है, तो type signature और public API ही जवाब दे देते हैं; और senior engineer के जाने पर भी ज्ञान बना रहता है

Durable execution और Temporal

  • वित्तीय सिस्टम के workflow एक ही transaction के भीतर सीमित नहीं रहते
    • payment transfer
    • partner approval का इंतज़ार
    • ledger update
    • customer notification
    • cancellation और timeout handling
    • जब partner सफल हो गया हो लेकिन worker रिकॉर्ड करने से पहले ही बंद हो गया हो
    • जब network समस्या के कारण कोई response न मिले
  • ऐसे flow के लिए state, retry, timeout, idempotency, और process crash तथा deployment के पार भी जारी रहने वाली execution की ज़रूरत होती है
  • Mercury पहले database-आधारित state machine, cron jobs, background workers, और कोड के अलग-अलग हिस्सों में retry व timeout handling के सहारे इन प्रक्रियाओं को orchestrate करता था
    • यह काम तो करता था, लेकिन नाज़ुक था, समझना कठिन था, और operational incidents का असंतुलित कारण बनता था
  • Temporal Mercury का durable execution framework है, जिसमें workflow को सामान्य sequential code की तरह लिखा जाता है और platform हर step को event history में रिकॉर्ड करता है
  • अगर worker workflow के बीच में crash हो जाए, तो दूसरा worker deterministic prefix को replay करके state दोबारा बनाता है और जहाँ रुकावट आई थी वहीं से आगे बढ़ता है
  • retry, timeout, cancellation, और error handling हर team द्वारा अलग से दोबारा implement करने के बजाय platform उपलब्ध कराता है
  • Temporal workflow का स्वभाव event history पर pure function जैसा होता है
    • replay किया गया workflow मूल workflow जैसी ही command sequence बनाना चाहिए
    • determinism की यह requirement pure code के same input, same output constraint जैसी है
    • side effects को workflow के IO के बराबर activity में isolate किया जाता है
  • Mercury ने Temporal के official Core SDK को Rust FFI से wrap करने वाला Haskell SDK hs-temporal-sdk बनाया और उसे open source कर दिया
  • Temporal अपनाने के pattern पर Temporal Replay conference presentation में भी चर्चा हुई, और Mercury ने नाज़ुक cron·state machine chain को durable workflow से बदलकर operations में सुधार पाया

डोमेन को transport layer नहीं, business language में डिज़ाइन करें

  • बढ़े हुए सिस्टम में एक आम गलती यह है कि calling system की अवधारणाएँ domain model में leak हो जाती हैं
  • HTTP request handler के लिए लिखा गया code जब बाद में cron job, queue-आधारित background worker, या Temporal workflow में reuse होता है, तब StatusCodeException 409 "Conflict" जैसे HTTP exceptions non-HTTP context तक फैल सकते हैं
  • cron job में 409 response का इंतज़ार करने वाला कोई caller नहीं होता, और status code business meaning को गलत layer में ले जाता है
  • इसका समाधान domain errors को domain types के रूप में model करना है
    • insufficient balance को InsufficientFunds होना चाहिए
    • duplicate request को DuplicateRequest होना चाहिए
    • partner timeout को PartnerTimeout होना चाहिए
  • हर boundary पर एक पतली transformation layer रखी जाती है
    data PaymentError  
      = InsufficientFunds  
      | DuplicateRequest RequestId  
      | PartnerTimeout Partner  
    
    toHttpError :: PaymentError -> HttpResponse  
    toHttpError InsufficientFunds       = err402 "Insufficient funds"  
    toHttpError (DuplicateRequest _)    = err409 "Duplicate request"  
    toHttpError (PartnerTimeout _)      = err502 "Partner unavailable"  
    
    toWorkerStrategy :: PaymentError -> WorkerAction  
    toWorkerStrategy InsufficientFunds    = Fail "Insufficient funds"  
    toWorkerStrategy (DuplicateRequest _) = Skip  
    toWorkerStrategy (PartnerTimeout _)   = RetryWithBackoff  
    
  • transport layer से जुड़ी चिंताएँ किनारों पर रहनी चाहिए, और domain model को web handler, CLI, cron job, background worker, या workflow engine कहीं से भी call किया जाए, वह HTTP status code अपने साथ नहीं लाना चाहिए

type encoding की लागत और सही संतुलन

  • invariants को type में डालना शक्तिशाली है, लेकिन इसकी कीमत cognitive cost, rigidity, और requirements बदलने पर आने वाली कठिनाई के रूप में चुकानी पड़ती है
  • अगर किसी violation से data loss, financial error, regulatory problem, या on-call incident हो सकता है, तो type encoding की लागत उचित हो सकती है
  • लेकिन अगर वजह सिर्फ यह है कि अभी चीज़ें ऐसे ही हैं, या type-level techniques आज़माने की इच्छा है, तो इससे codebase बदलना मुश्किल हो सकता है
  • ज़रूरत से ज़्यादा encoding करने वाला पक्ष

    • illegal states को represent करना असंभव हो जाता है और domain को types में ईमानदारी से model किया जाता है
    • business rule बदलने पर 50 modules में type changes फैल जाते हैं, जिससे refactoring लंबी हो जाती है
    • नए engineers के लिए type signatures समझना कठिन हो जाता है
  • कुछ भी encode न करने वाला पक्ष

    • types String, IO (), और सबसे खराब स्थिति में Dynamic के करीब पहुँच जाते हैं
    • code बदलना आसान होता है, लेकिन कोई contract नहीं होता और अर्थ पुराने लेखक की याददाश्त पर निर्भर रहता है
    • लेखक के चले जाने पर यह समझना मुश्किल हो जाता है कि सिस्टम क्यों काम नहीं कर रहा
  • उपयोगी मानदंड

    • जो invariants silent corruption रोकते हैं, उन्हें type में डालना बेहतर है
      • event के बिना committed transaction
      • audit log के बिना processed payment
      • ऊपर से संभव दिखने वाले लेकिन अर्थ की दृष्टि से असंभव state transitions
    • जो invariants ज़ोर से fail होते हैं, उनके लिए अच्छे error messages वाली runtime checks काफ़ी हो सकती हैं
      • 500 response
      • assertion failure
      • JSON boundary पर type mismatch
    • पूरे domain को types में model करने की इच्छा को नियंत्रित रखना चाहिए
      • domain में exceptions, backward-compatibility rules, परस्पर टकराने वाले rules, और कुछ खास customers के लिए special behavior मौजूद होते हैं
    • types सिर्फ compiler के लिए नहीं, team के लिए भी एक tool हैं
      • उन्हें tests, documentation, code review, examples, और playbooks के साथ मिलकर एक defense layer बनानी चाहिए
    • Mercury के भीतर कुछ libraries ऐसी भी हैं जो GADT, type family, और state transition को track करने वाले phantom type जैसे जटिल type-level mechanisms का उपयोग करती हैं
    • जहाँ गलती होने पर पैसा गलत जगह चला जाए या regulatory invariants टूट जाएँ, वहाँ ऐसी जटिलता ज़रूरी हो सकती है
    • मुख्य बात इस जटिलता को encapsulate करना है
    • type-level state machine implement करने वाले module के पास कुछ ही ऐसे authors होने चाहिए जो उसे गहराई से समझते हों, और पर्याप्त tests होने चाहिए
    • उसे इस्तेमाल करने वालों के लिए API कुछ सामान्य types वाली functions जैसा दिखना चाहिए
    • product engineer को अंदर के type-level proof mechanisms जाने बिना भी उसे सुरक्षित रूप से call कर पाना चाहिए
    • अगर code review में किसी दूसरे module को छूने वाला PR compiler को मनाने के लिए कॉपी किए गए type annotations से भरा हो, तो यह संकेत है कि abstraction अपनी सीमा से बाहर leak हो रही है

अंतर्दृष्टि-योग्यता के लिए डिज़ाइन

  • अगर reliability अनुकूलन की क्षमता है, तो introspectability उस क्षमता को हासिल करने के तरीकों में से एक है
  • ऑपरेटर उस चीज़ को ऑपरेट नहीं कर सकते जिसे वे देख नहीं सकते, और टीमों के लिए ऐसे सिस्टम के अनुसार ढलना मुश्किल होता है जिनका अंदरूनी हिस्सा अपारदर्शी हो
  • Haskell में monkey patching नहीं है, इसलिए runtime पर किसी लाइब्रेरी के अंदर के HTTP client को बदलना या database call को OpenTelemetry span निकालने वाले फ़ंक्शन से बदलना मुश्किल है
  • Rust में भी यही सीमा है, लेकिन Rust ecosystem tower middleware pattern पर आकर काफ़ी हद तक एकजुट हो गया है, जबकि Haskell ecosystem कई तरीकों में बँटा हुआ है
  • अगर कोई लाइब्रेरी सिर्फ़ concrete top-level functions का सेट expose करती है, तो instrumentation के लिए उसे नए module में wrap करना पड़ता है और उम्मीद करनी पड़ती है कि लोग original module की जगह वही import करें
  • फ़ंक्शन रिकॉर्ड

    • सबसे ज़्यादा इस्तेमाल होने वाला समाधान concrete functions की जगह फ़ंक्शन रिकॉर्ड expose करना है
      -- A concrete module gives you no leverage:  
      sendRequest :: Request -> IO Response  
      -- A record of functions gives you all of it:  
      data HttpClient = HttpClient  
      { sendRequest :: Request -> IO Response  
      , getManager  :: IO Manager  
      }  
      
    • इस तरीके में sendRequest को timing instrumentation से wrap करके नया HttpClient लौटाया जा सकता है
    • test के लिए fault injection, mock replacement, retry, tracing, request rewrite, tenant-वार behavior जैसे cross-cutting concerns runtime पर जोड़े जा सकते हैं
    • WAI के type Middleware = Application -> Application की तरह behavior transformation को composable बनाने वाला pattern संचालन के नज़रिए से बहुत उपयोगी है
  • Monoid से compose होने वाले interceptor

    • middleware और interceptor types आम तौर पर Semigroup और Monoid instances रख सकते हैं
    • WAI का Middleware एक endomorphism है, और endomorphism composition और id के तहत monoid बनाते हैं
    • interceptor hook records को field-दर-field compose किया जा सकता है, इसलिए tracing, timeout, task queue rewrite जैसे concerns को अलग plumbing के बिना mconcat से जोड़ा जा सकता है
      appTemporalInterceptors =  
      mconcat  
        [ retargetingInterceptor  
        , otelInterceptor  
        , sentryInterceptor  
        , sqlApplicationNameInterceptor  
        , loggingContextInterceptor  
        , statementTimeoutInterceptor  
        , teamNameInterceptor  
        , clientExceptionInterceptor  
        , workflowTypeNameInterceptor  
        ]  
      
    • हर interceptor अलग module में सिर्फ़ एक concern संभालता है, mempty से ज़रूरी fields ही override करता है, और क्रम list में साफ़ लिखा होता है
  • effect system

    • effectful, polysemy, fused-effects, cleff जैसे effect system भी एक दूसरा रास्ता देते हैं
    • उपलब्ध operations को effect types के रूप में define किया जाता है और production, testing, tracing के लिए interpreter को call site पर बदला जा सकता है
    • effect को intercept करके metrics record किए जा सकते हैं या delay inject करने के बाद उसे वापस असली handler को भेजा जा सकता है
    • कमी यह है कि type-level effect list, handler stack, और मुश्किल type errors जैसी अतिरिक्त जटिलताएँ जुड़ जाती हैं
    • फ़ंक्शन रिकॉर्ड इतने सरल होते हैं कि नया engineer इन्हें एक ही दोपहर में समझ सकता है
  • persistent का सकारात्मक उदाहरण

    • persistent का SqlBackend एक फ़ंक्शन रिकॉर्ड है, जिसमें connPrepare, connInsertSql, connBegin, connCommit, connRollback जैसे फ़ंक्शन होते हैं
    • OpenTelemetry instrumentation जोड़ते समय संबंधित fields को wrap करके सभी database operations पर tracing span जोड़ा जा सका
    • fork किए बिना, और source में लगभग कोई बदलाव किए बिना, database layer की visibility हासिल हो गई
  • संचालन के लिहाज़ से कठिन लाइब्रेरी

    • Mercury Hackage पर प्रकाशित web API client bindings का लगभग इस्तेमाल नहीं करता
    • अगर third-party binding concrete functions से HTTP calls करती है, तो tracing, SLO के अनुरूप timeout, partner outage simulation, या trace में 400ms के खाली हिस्से की व्याख्या करना मुश्किल हो जाता है
    • इसलिए वे client ख़ुद लिखते हैं और शुरुआत से ही उन्हें observable बनाते हैं
  • छोटे ecosystem की लागत

    • कुछ Haskell libraries छोड़ी हुई नहीं हैं, लेकिन वे ऐसी public infrastructure जैसी रह गई हैं जिनकी साफ़ ज़िम्मेदारी लेकर तेज़ी से सुधार करने वाला कोई नहीं है
    • पुराने interfaces बने रहते हैं, और observability, boundary design, तथा operability से जुड़ी नई design को अपनाने की रफ़्तार धीमी हो सकती है
    • http-client सीधे तौर पर सिर्फ़ HTTP/1.1 को support करता है; यह काफ़ी उपयोगी है, लेकिन कुछ मौकों पर workaround की ज़रूरत पड़ सकती है

package authors के लिए संचालन संबंधी आवश्यकताएँ

  • library authors को users के लिए ऐसे escape hatches देने चाहिए, जैसे फ़ंक्शन रिकॉर्ड, effect types, या callback, ताकि वे source बदले बिना behavior inject कर सकें
  • dependency के रूप में hs-opentelemetry-api जोड़ना और core IO operations के आसपास span रखना भी उन users के लिए मददगार होता है जो production में library चलाते हैं
    • API package breaking changes को लेकर conservative है, और अगर application OpenTelemetry SDK initialize नहीं करती तो इसे inert तरीके से काम करने के लिए डिज़ाइन किया गया है
    • performance overhead न्यूनतम रखा गया है, और यह user application में unexpected exceptions या logging उत्पन्न नहीं करता
    • dependency footprint अभी उतना छोटा नहीं है जितना वांछित है, और इस पर सुधार का काम चल रहा है
  • library code के अंदर सीधे log नहीं लिखने चाहिए
    • logging framework import करके सीधे stdout या stderr में लिखने के बजाय callback, logger parameter, या ऐसा log message data type देना चाहिए जिसे caller route कर सके
    • logs कहाँ जाएँगे, यह application के operational environment का फ़ैसला है
    • Mercury structured log pipeline को observability stack तक भेजता है, और अगर कोई library सीधे stderr में लिखती है तो JSON lines stream से अलग plumbing की ज़रूरत पड़ती है
  • .Internal modules expose करने पर भी विचार किया जा सकता है
    • यह चिंता वाजिब है कि users internal API पर निर्भर हो जाएँगे और refactor मुश्किल हो जाएगा
    • लेकिन यह भरोसा कि public API पहले से हर use case को कवर कर चुकी है, बहुत कम ही सही साबित होता है
    • स्पष्ट stability warning वाले .Internal modules, users के लिए package को fork करके vendoring करने से बेहतर हो सकते हैं
    • containers, text, unordered-containers Haskell ecosystem में इस तरीके के अच्छे उदाहरण हैं
    • हालाँकि अगर users चुपचाप internal modules से अपनी ज़रूरत पूरी कर लें, तो public API की कमियों पर feedback कम हो सकता है

टाइप में शामिल न होने वाली चीज़ें

  • production Haskell में भी कुछ ऐसे हिस्से होते हैं जो सुंदर नहीं होते
  • unsafePerformIO का उपयोग उन लाइब्रेरीज़ के भीतर किया जाता है जिन पर हम रोज़मर्रा में निर्भर रहते हैं
    • bytestring और text अंदरूनी तौर पर mutable buffer allocate करते हैं, उसमें लिखते हैं, फिर उसे freeze करके परिणाम बनाते हैं
    • टाइप यह नहीं बताता कि निर्माण के दौरान क्या हुआ था
    • सीमाएँ परंपरा, सावधानीपूर्वक reasoning, और code review से बनाए रखी जाती हैं
  • अगर type-safe विकल्प performance या complexity की लागत को बहुत ज़्यादा बढ़ा दें, तो कभी-कभी ऐसे समझौते सीधे लिखने पड़ सकते हैं
    • उन invariants को document करना चाहिए जिन्हें टाइप verify नहीं करता
    • असुविधा को बनाए रखना चाहिए, और समय-समय पर दोबारा जाँचना चाहिए कि क्या type-safe विकल्प व्यावहारिक हो गए हैं
    • production Haskell का मतलब समझौतों की अनुपस्थिति नहीं, बल्कि समझौतों का अनुशासित पृथक्करण है
  • Hackage की कई Haskell लाइब्रेरीज़ में tests कम हैं या बिल्कुल नहीं हैं
    • “अगर compile हो जाता है, तो यह काम करेगा” जैसी सोच कभी-कभी छोटे pure code और strong types में सही हो सकती है
    • लेकिन IO-heavy code, external systems के integration, और ऐसे code के लिए जिसमें bug संरचना में नहीं बल्कि अर्थ में हो, यह लगभग कभी सही नहीं होती
  • टाइप यह बता सकता है कि Either ParseError Transaction लौटाया जाता है, लेकिन यह नहीं बता सकता कि
    • amount field को cents में parse किया जा रहा है या dollars में
    • partner API omitted field और null field को अलग-अलग तरह से interpret करता है या नहीं
    • retry logic leap year के किसी खास timing window में double charge करा सकता है या नहीं
  • production में ऐसे ही libraries के ऊपर systems बनाए जाते हैं, और unverified assumptions विरासत में मिलते हैं, इसलिए इन्हें अपनी layer के integration tests से पूरा करना पड़ता है
  • orphan instance, ऐसी partial function जिसे संदर्भ के आधार पर total माना गया हो, ऐसा error जिसके unreachable होने का वादा किया गया हो, अटपटा FFI wrapper, और hand-written exception hierarchy जैसे समझौते भी जमा होते जाते हैं
  • लक्ष्य नैतिक शुद्धता नहीं, बल्कि यह है कि हर समझौता कहाँ है, क्यों किया गया था, और उसे हटाने पर क्या टूटेगा—यह code review, documentation, examples, और tests के ज़रिए पता चल सके

production में Haskell इस्तेमाल करने की अहमियत

  • Haskell पहले दिन से तेज़ विकल्प नहीं है
    • मौजूदा ecosystem अभी Next.js या Rails जैसा batteries-included hot-reloading development environment तुरंत उपलब्ध नहीं कराता
    • ज़रूरी library मौजूद न हो सकती है, या हो तो भी उसे कोई एक व्यक्ति अपने spare time में maintain कर रहा हो सकता है
    • कभी-कभी error messages बेहद दुरूह हो सकते हैं
  • hiring की समस्या बढ़ा-चढ़ाकर बताई जाती है
    • Mercury के CTO Max Tagher सार्वजनिक रूप से कह चुके हैं कि backend Haskell engineer, Mercury में सबसे आसानी से hire होने वाली भूमिका है
    • Haskell jobs की demand supply से ज़्यादा होने के कारण सामान्य hiring dynamics उलट जाते हैं
    • Mercury ऐसे लोगों को भी hire करता है जिनके पास Haskell का गहरा अनुभव है, और ऐसे लोगों को भी जिनके पास बिल्कुल नहीं है; बाद वाले 6~8 हफ्तों के training program के ज़रिए productive बनते हैं
    • अगर कल ही 100 Haskell experts चाहिए हों, तो hiring pool की समस्या वास्तविक है; लेकिन अगर आप अच्छे generalist developers को hire करके उन्हें सिखाने के इच्छुक हैं, तो यह उतनी वास्तविक नहीं है
  • बड़ा hiring risk pool का आकार नहीं बल्कि झुकाव है
    • Haskell उन idealists को आकर्षित करता है जो correctness और abstraction की परवाह करते हैं, papers पढ़ना पसंद करते हैं, और मौजूदा assumptions पर सवाल उठाते हैं
    • अगर इस ताकत को नियंत्रित न किया जाए, तो यही production liability बन सकती है
    • database layer को किसी नए type-level relational algebra encoding से फिर से लिखने की कोशिश करना, throwaway script में String की जगह Text न इस्तेमाल करने पर merge ठुकरा देना, या हर design को नवीनतम paper-शैली के total rewrite की ओर धकेलना—ये सब टीम को धीमा कर देते हैं
  • production Haskell के लिए pragmatism की संस्कृति चाहिए
    • type system एक power tool है, धर्म नहीं
    • जिस समस्या का अच्छा समाधान पहले से मौजूद है, उसे नया mechanism invent करने का मौका मानना production के लिए उपयुक्त नहीं है
  • लाभ समय के साथ सामने आता है
    • dynamic typed codebase में जो refactoring हफ्तों ले सकती है, वही type बदलने के बाद compiler द्वारा सभी call sites बता देने से कुछ घंटों में पूरी हो सकती है
    • नया engineer type signature पढ़कर module के contract को समझ सकता है
    • संभव है कि production incident इसलिए न हो, क्योंकि impossible states वास्तव में represent ही नहीं की जा सकतीं
  • Mercury का मानना है कि निवेश की वापसी सालों में नहीं बल्कि कुछ महीनों में दिखती है
    • खासकर financial services में, data integrity bugs की कीमत user complaints से नहीं बल्कि regulatory findings और दूसरे लोगों के पैसे से मापी जाती है
    • type system जोखिम को खत्म नहीं करता, लेकिन यह ऐसे tools देता है जो तेज़ी से बढ़ते codebase में गलती से जोखिम शामिल करना कठिन बना देते हैं
  • Haskell की production value कोई silver bullet या नैतिक आंदोलन नहीं है, बल्कि शक्तिशाली tools के ऐसे set में है जो अलग-अलग Haskell skill level वाली teams को खतरनाक तंत्रों को सीमाओं के भीतर रखने, operational knowledge को संरक्षित रखने, और safe path को easy path बनाने में सक्षम बनाता है

1 टिप्पणियां

 
GN⁺ 2 시간 전
Hacker News की टिप्पणियाँ
  • यह सही है कि Haskell उन भाषाओं में से एक है जो इस तरह की चीज़ों को type के ज़रिए लागू कराने में सबसे शक्तिशाली है, लेकिन यही pattern Rust और TypeScript में भी काफ़ी अच्छी तरह काम करता है
    वेबऐप्स में बार-बार आने वाले साफ़-साफ़ दिखने वाले authorization bugs को User -> LoggedInUser -> AccessControlledLoggedInUser जैसी flow से रोकने का तरीका भी मुझे पसंद है
    मुझे लगता है कि इंडस्ट्री में इस pattern का इस्तेमाल बहुत कम होता है

    • यह सिर्फ Rust या TypeScript तक सीमित नहीं है, बल्कि लगभग हर भाषा में संभव है
      अगर security के लिहाज़ से escaped और unescaped strings में फ़र्क करना हो, तो dynamic typed language में भी Escaped class में wrap करके escape(str)->Escaped, dangerouslyAssumeEscaped(str)->Escaped जैसी functions रखी जा सकती हैं
      performance cost होती है, इसलिए कुछ समझौता करना पड़ता है, लेकिन यह संभव है
      एक और तरीका Application Hungarian है, हालांकि यह compiler से ज़्यादा programmer की discipline पर निर्भर करता है: https://www.joelonsoftware.com/2005/05/11/making-wrong-code-...
    • यह type system से ज़्यादा affordance का मामला है
      उदाहरण के लिए C# में भी यह पूरी तरह किया जा सकता है, लेकिन वहाँ असली type definition से ज़्यादा visual noise बढ़ जाता है
    • Rust और TypeScript दोनों पर Haskell का प्रभाव साफ़ है
      बस वे आम तौर पर इसे खुलकर नहीं कहते और नाम भी अलग रखते हैं, ताकि “monad डरावने हैं इसलिए tutorial लिखना पड़ेगा” जैसी प्रतिक्रिया से बचा जा सके
      monad की तुलना में type class जैसी चीज़ों का असर ज़्यादा है
    • मुझे भरोसा नहीं कि TypeScript में यह वाकई बहुत अच्छी तरह होता है
      इसमें nominal types नहीं हैं, इसलिए primitive types को wrap करने वाले newtype जैसी चीज़ बनाने के लिए काफ़ी hacky मंत्र याद रखने पड़ते हैं
      मेरे अनुभव में इस तरह की type safety लागू कराने में OCaml, Rust से ज़्यादा ताकतवर था
      GADT की वजह से इसकी expressiveness ज़्यादा है, और polymorphic variants तथा object types/record row types की वजह से convenience भी मिलती है, साथ ही module system और functors भी हैं
      जहाँ garbage collection काफ़ी है, वहाँ Rust के borrow checker से आने वाली abstraction limits और मुश्किलों से भी बचा जा सकता है
    • यह मूलतः “गलत states को represent करना असंभव बना दो” वाली बात है: https://news.ycombinator.com/item?id=40150159
  • मुझे कई साल तक Haskell में काम करना सचमुच बहुत पसंद था
    मैंने इसे जानबूझकर नहीं खोजा था, लेकिन मौका संयोग से मिला और यह रोचक व बौद्धिक रूप से बहुत प्रेरक था
    लेकिन अफ़सोस, Haskell में 3 साल बिताने के बाद भी Rust में मेरी productivity Haskell से आसानी से दोगुनी है
    Haskell में पहले से जानकर बचने लायक़ pitfalls ज़्यादा हैं, और लेखक पर निर्भर करते हुए कभी-कभी यह लगभग read-only language जैसा लगने लगता है
    toolchain अक्सर Nix के साथ बंधी होती है, और Nix खुद भी एक जटिल दैत्य है, ऊपर से language extensions हर तरफ़ फैली हुई लगती हैं
    Cabal files भी अच्छे नहीं लगते, और compiler errors की आदत पड़ने में समय लगता है

    • काफ़ी हैरानी की बात है कि मेरा अनुभव लगभग बिल्कुल उल्टा था
      अपने पिछले product में हमने backend को Typescript से Rust पर port करना शुरू किया था, क्योंकि crashes से तंग आ चुके थे
      अब मैं उसे अपनी सबसे बड़ी technical mistakes में से एक मानता हूँ, क्योंकि productivity बहुत धीमी हो गई
      Rust में ही हुआ एक समय-बरबाद करने वाला उदाहरण यह था कि database connection खोलकर कुछ करना और फिर बंद करना जैसी higher-order function लिखना Haskell, TypeScript, JavaScript, C++, PHP में तुच्छ था, लेकिन Rust में Rust expert दोस्तों से पूछने पर भी यह व्यावहारिक रूप से असंभव निकला, और हमें हार माननी पड़ी
      कई बार ऐसा हुआ कि refactoring की कोशिश में पूरा दिन type errors ठीक करते रहे, फिर सबसे ऊपर की file में error मिला, और तब समझ आया कि design के किसी बुनियादी हिस्से की वजह से पूरी refactoring असंभव है, इसलिए सब वापस रोलबैक करना पड़ा
      इसके अलावा Rust शायद इकलौती modern language है जहाँ concrete types की जगह interfaces के रूप में values का इस्तेमाल संदर्भ के हिसाब से advanced technique और outright impossible के बीच कहीं आता है
      इसलिए मैं इस निष्कर्ष पर पहुँचा कि application code, यानी system code या library code नहीं, उसे लगभग Rust में नहीं लिखना चाहिए
    • क्या productivity कुल मिलाकर 2x थी, या Rust में कुछ हिस्से ऐसे भी थे जहाँ productivity कम थी?
      और “read-only” से आपका मतलब क्या है, यह भी जानना चाहूँगा
  • आम धारणा के विपरीत, मुझे लगता है कि Mercury ने Haskell चुना और शुरुआती नेताओं के पास Haskell का गहरा अनुभव था — इसने उसकी सफलता में कम नहीं बल्कि काफ़ी बड़ा योगदान दिया होगा
    Mercury के ग्राहक के नज़रिए से यह कंपनी मेरे toolkit की सबसे अहम कंपनियों में से एक है, और यह भावना हटती नहीं कि Haskell चुनने से उनकी प्रगति, development और पूरी journey बेहतर हुई
    बेशक, ऐसा दावा लगभग हर भाषा के बारे में किया जा सकता है, और इसका मतलब यह नहीं कि Haskell जैसी functional language ही सफलता का फ़ॉर्मूला है
    लेकिन “vibe coding” और LLM युग से पहले ऐसा सोच-समझकर लिया गया फ़ैसला ख़ास तौर पर दूरदर्शी लगता है, और यह लेख में विस्तार से बताए गए engineering culture के साथ मिलकर काम करता दिखता है

    • सफलता का कारण शायद startup-oriented fintech focus और execution ज़्यादा है
      मुझे भी अच्छी tech culture पसंद है, लेकिन मैंने बेहतरीन tech culture वाली कंपनियों को खराब business focus की वजह से मरते देखा है
      आगे बढ़कर यह भी हो सकता है कि startup-style fintech culture ने ही अच्छी tech culture को जन्म दिया हो
      क्योंकि उन्होंने bank के रूप में शुरुआत नहीं की, इसलिए उदाहरण के लिए SVB की तरह उन्हें इतना conservative होने की ज़रूरत नहीं थी, और न ही भयानक ancient tech stack के साथ integrate करना पड़ा
      Haskell के साथ उनकी सफलता देखकर खुशी होती है, लेकिन Jane Street और OCaml की तरह, कंपनी चाहे जो मानना चाहती हो, business perspective से language choice लगभग accident जैसी लगती है
      हाँ, यह जानने की जिज्ञासा ज़रूर है कि frontend में वे क्या इस्तेमाल करते हैं। शायद यह सारा Haskell backend में ही है
    • जिस तरह उन्होंने उस भाषा का अनुभव न रखने वाले generalists को hire किया, उससे भी मदद मिली होगी
      क्योंकि वे नए लोगों में culture और style शुरू से ही बैठा सकते थे
      अगर यह vibe coding से पहले का समय था, तो ऐसे ज़्यादातर लोग बिना किसी guidance के बस कूदकर hacking शुरू नहीं कर देते
    • ऐप में सब कुछ बस सही तरह काम करता है, यह बात मैंने महसूस की
      दूसरी services से switch करके आएँ तो यह सचमुच बहुत संतोषजनक लगता है
  • मेरा एक बहुत करीबी दोस्त इस कंपनी में काम करता है, और बाहर से देखने पर भी engineering culture बहुत अच्छी लगती है
    Haskell इस काम के लिए सही tool है और वे इसकी strengths का अच्छा इस्तेमाल कर रहे हैं, लेकिन मुझे यह भी लगता है कि उनकी सफलता का बड़ा हिस्सा शायद सिर्फ़ इस वजह से है कि कंपनी कुल मिलाकर बहुत अच्छे से चलाई जा रही है

    • लेख पढ़ते समय मुझे भी यही एहसास हुआ
      यह लेखक शायद किसी भी language का इस्तेमाल करता, तब भी एक सफल engineering organization बना लेता
    • यह उस आम धारणा से भी टकराता नहीं कि functional programming languages इस्तेमाल करने से बेहतर quality वाले लोग/उम्मीदवार छँटकर आते हैं
  • मैं अभी Real-World OCaml पढ़ रहा हूँ, कुछ बातें पहले से जानता था, लेकिन functional programming के बारे में और ज़्यादा सीख रहा हूँ
    ऐसा लगता है कि functional programming से चौंकाने वाली तरह robust software बनाया जा सकता है
    लेकिन कुछ चिंता भी है
    मेरा मौजूदा product backend NiceGUI पर चलता है और अपना काम ठीक से करता है
    code ठीक-ठाक, MVVM शैली का है, और सबसे अहम काम है ग्राहक-दर-ग्राहक websockets से जुड़ना, data consume करना और analysis दिखाना
    ग्राहकों की संख्या बहुत ज़्यादा नहीं होगी, और वेबसाइट visitors शायद कुछ दर्जन से लेकर ज़्यादा से ज़्यादा कुछ सौ तक होंगे
    मैं REPL या hot reload भी चाहता हूँ, और समझता हूँ कि features बढ़ने पर user management panel, ज़्यादा analysis वगैरह में functional programming data pipeline transformations के लिए अच्छी fit हो सकती है
    लेकिन Haskell या OCaml static languages हैं
    अगर बाद में scale करने के साथ-साथ dynamic भी चाहिए, तो Clojure या Elixir अच्छे विकल्प लगते हैं
    साथ ही, कभी refactoring की ज़रूरत पड़ी तो सब टूट जाने का डर भी है
    अभी मैं Python और Mypy इस्तेमाल करता हूँ, और frontend NiceGUI backend से generate करता है

    • OCaml के बारे में नहीं जानता, लेकिन Haskell में ghci/cabal repl के साथ development के दौरान webapp को बहुत तेज़ी से reload किया जा सकता है
      सच कहूँ तो मुझे लगता है कि बहुत से Haskell users इसका पूरा फ़ायदा नहीं उठाते
  • मैंने Scheme, और बाद में Racket जैसी अपेक्षाकृत niche language में एक ऐसा ही system बनाया था, जो scale तो हुआ लेकिन लंबे समय तक छोटी team द्वारा maintainable और तेज़ बना रहा
    हमने बहुत ज़्यादा bugs नहीं बनाए, और आम तौर पर features बहुत तेज़ी से जोड़ सकते थे
    उदाहरण के लिए AWS पर sensitive data host करने के लिए एक तरह का certification हमने सबसे पहले हासिल किया था
    कभी-कभी हमें ऐसी चीज़ें scratch से बनानी पड़ती थीं, जिन्हें popular platforms पर off-the-shelf components से सुलझा लिया जाता, इसलिए feature delivery धीमी हो जाती थी
    लेकिन एक बार बन जाने के बाद वे अच्छे से चलती थीं, और हम फिर से अपनी पुरानी रफ़्तार पर लौट आते थे, दर्जनों off-the-shelf frameworks की bloated complexity से धीमे हुए बिना
    क्योंकि हम एक manageable platform पर खुद control रखते थे, ज़रूरत पड़ने पर AWS पर जल्दी move भी कर सकते थे
    system में शुरुआत से ही complex data और web interactions के लिए architectural magic भी था, जिसने कई features तेज़ी से बनाने में मदद की और आगे भी समझदारी भरी दिशा में momentum दिया
    Haskell fintech से फ़र्क यह था कि हमारी team बहुत छोटी थी
    किसी भी समय सिर्फ़ 2–3 software engineers होते थे, और एक व्यक्ति operations भी संभालता था
    इसलिए सैकड़ों लोगों के बीच coordination रखते हुए consistent system बनाए रखने की समस्या नहीं थी
    आम तौर पर एक व्यक्ति ज़्यादा technical और architectural code changes संभालता था, जबकि दूसरा complex processes के लिए बहुत सारा business logic जल्दी जोड़ता था
    मुझे लगता है कि अगर आज या निकट भविष्य के LLM-जैसे AI tools का सावधानी से इस्तेमाल किया जाए, तो software development में भी बहुत छोटी लेकिन बेहद प्रभावी teams वाली कुछ efficiency पाई जा सकती है
    उभरता हुआ model यह नहीं है कि story points मिटाने के लिए विशाल bloated output पैदा किया जाए और sustainability किसी और की समस्या बना दी जाए, बल्कि यह है कि कुछ बेहद तेज़ सोच वाले लोग system को ताकत भी दें और उसे manageable रास्ते पर भी बनाए रखें

  • यह दोधारी तलवार है
    20 लाख lines एक शानदार उपलब्धि है, लेकिन साथ ही यह एक बड़ा maintenance burden भी है
    Haskell के फ़ायदे सिद्धांत में साफ़ हैं, लेकिन इसकी कमियाँ सहज रूप से समझना मुश्किल है
    प्रलोभन यह रहता है कि हर चीज़ को types में model कर दिया जाए
    codebase खुद application नहीं बल्कि business specification बन जाता है
    policy में हर बदलाव बड़े refactoring में बदल जाता है, और Haskell की safety की वजह से यह चौंकाने वाली तरह मेहनत-तलब भी हो सकता है
    आख़िरकार आप दोनों चीज़ें साथ नहीं रख सकते, और किसी न किसी दिन type में फँस ही जाते हैं
    Haskell ख़ासकर इस scale पर सचमुच प्रभावशाली और शक्तिशाली है, लेकिन अपने अनोखे problems भी लाता है
    business logic को types में model करने का प्रलोभन rigid structure बना सकता है, और उस structure से मिलने वाली safety दूसरी तरह के risks को देखना मुश्किल कर सकती है

    • अगर core हिस्से taste वाले अनुभवी engineers बनाते हैं, तो वे इस संतुलन को काफ़ी अच्छी तरह साध सकते हैं
      सब कुछ नहीं मिल सकता, लेकिन बहुत कुछ मिल सकता है
      मैंने कुछ साल पहले Jane Street में intern किया था; Haskell नहीं बल्कि OCaml था, लेकिन वे यह balance बहुत अच्छी तरह साधते दिखे
      यह ऐसा क्षेत्र है जहाँ intrinsic complexity बहुत ज़्यादा है और reliability व correctness सीधे business survival से जुड़ी है, फिर भी वे चौंकाने वाली गति से आगे बढ़ते थे
      पीछे मुड़कर देखने पर, Jane Street की असली ताकत यह थी कि उन्होंने Stephen Weeks जैसे बेहतरीन taste वाले अनुभवी OCaml programmers को hire किया, उन्हें शुरुआत से core libraries बनाने दीं और पूरे codebase को guide करने दिया
      दुर्भाग्य से Mercury यह हिस्सा उतनी अच्छी तरह नहीं कर पाया
    • TypeScript में भी यही बात लागू होती है: https://www.richard-towers.com/2023/03/11/typescripting-the-...
      ईमानदारी से कहूँ तो Turing-complete type system की सबसे बड़ी कमी यह है कि सैद्धांतिक रूप से आप ऐसे applications implement कर सकते हैं जो compile होते-होते धूल बन जाएँ
  • Bellroy की इसी तरह की Haskell success story आने वाली Melbourne Compose meetup का विषय है: https://luma.com/uhdgct1v

  • functional programming में मेरी समस्या debugging है
    या ज़्यादा सही कहूँ तो imperative programming, ख़ासकर procedural style, की ताकत यही लगती है
    functional/declarative style में आप आम तौर पर यह नहीं बताते कि चीज़ कैसे बनती है, बल्कि यह बताते हैं कि state कैसी होनी चाहिए, और भाषा सब कुछ जोड़कर अंतिम result दे देती है
    अगर सब कुछ सही हुआ तो अच्छा है, बल्कि शायद बेहतर भी, लेकिन अगर ऐसा नहीं हुआ और अपेक्षित result नहीं मिला, तो सवाल यह है कि bug खोजेंगे कैसे
    C जैसी भाषा में यह अपेक्षाकृत सरल होता है
    आप line-by-line चलते हैं, हर step के बीच execution state यानी लगभग RAM देखते हैं, और जहाँ अपेक्षा से अलग मिले वहीं कुछ गलत है, फिर अंदर जाकर आगे बढ़ते हैं
    functional programming में भाषा जितना state को छिपाने की कोशिश करती है, यह उतना कठिन हो जाता है
    लेख का सबसे लंबा section इसी समस्या, यानी “design for introspection”, पर होना भी दिलचस्प है
    लेखक को code को debuggable बनाने के लिए जानबूझकर बहुत मेहनत करनी पड़ी, और यह Haskell के अक्सर नज़रअंदाज़ किए जाने वाले practical use पर अच्छा insight देता है

    • मेरी debugging trick यह है कि थोड़ी भी महत्वपूर्ण हर code path, एक ही input पर वही output लौटाए
      मामूली code के लिए भी यही
      दूसरी mainstream languages इसके करीब भी नहीं आतीं
      shared-memory concurrency जैसी situations जहाँ यह संभव नहीं, वहाँ transactions इस्तेमाल करो
      इसमें भी दूसरी mainstream languages पास नहीं आतीं
      और मैंने null न होना, implicit integer casting न होना जैसी आसान खूबियाँ तो गिनाई भी नहीं हैं
      यह पूरी तरह सही है कि Haskell code debug करना दूसरी भाषाओं से ज़्यादा मुश्किल है
      लेकिन जब आप नीचे के 90% टांग खींचने वाले मुद्दे हटा देते हैं, तो स्वाभाविक है कि ऐसा होगा
    • imperative programming के विपरीत functional programming में debugging अक्सर REPL-driven होती है
      बेशक यह सिर्फ functional तक सीमित नहीं है; Python या JavaScript जैसी ज़्यादातर imperative भाषाओं में भी Python shell, browser console, Node/Deno/Bun shell, notebooks आदि को debugging की पहली परत के रूप में इस्तेमाल किया जाता है
      REPL-केंद्रित debugging में दिलचस्प trade-offs होते हैं
      C जैसी भाषा में अक्सर पूरा program debug किया जाता है और breakpoints से शुरुआत कर उस सटीक जगह का अनुमान लगाया जाता है जहाँ समस्या हो सकती है
      REPL-केंद्रित दुनिया में लोग program के components को सीधे REPL में ज़्यादा test करने योग्य बनाना चाहते हैं
      इसलिए module/API/type boundaries, debuggability जैसी लगने लगती हैं
      कभी-कभी C/C++ जैसी imperative भाषाओं की तुलना में इन boundaries को सही और उपयोगी बनाकर लिखने का दबाव ज़्यादा होता है
      दूसरी ओर, पूरे program-प्रथम debugging की तुलना में, वास्तविक दुनिया के अजीब scenarios में units के बीच के complex integration issues को अलग करना और कठिन हो सकता है
      फिर भी REPL-first approach अक्सर integration surface area को कम से कम रखने के लिए प्रेरित करती है, इसलिए functional languages में imperative languages वाले integration effects कम दिखते हैं
      यह कहना सही नहीं कि functional languages state को छिपाती हैं
      वे भी imperative hardware पर चलती हैं और असली hardware state से ही काम करती हैं
      किसी बिंदु पर दोनों दुनियाओं के बीच translation होता है, और शायद वह आपकी सोच से इतना अलग भी नहीं होता
      ज़रूरत पड़ने पर आप फिर भी imperative breakpoints और imperative debugger पर लौट सकते हैं
      इसलिए मैं इसे “REPL-driven” debugging कहता हूँ
      REPL से आप समस्या वाली unit, यानी सटीक module/API/function और वह input जो चौंकाने वाला output दे रहा है, उसे संकुचित कर सकते हैं
      अगर source देखकर भी bug न मिले, तो उसे imperative debugger में भेजकर लगभग वही line-by-line अनुभव पाया जा सकता है, बल्कि अतिरिक्त context भी मिल सकता है
      तब तक आप REPL से इसे इतना narrow कर चुके होंगे कि unit खुद ही छोटी और सीमित होगी, इसलिए अच्छे breakpoints चुनने की ज़रूरत भी शायद ज़्यादा नहीं बचेगी
      लेख के “design for introspection” section से जो संदेश लिया गया, वह शायद गलत था
      वह section debuggability के बारे में नहीं बल्कि observability के बारे में था
      उसमें logging/telemetry system को सही तरह wire करना, tests में fakes को mock करना, और individual libraries पर छोड़ने की बजाय system-wide स्तर पर retries/circuit breakers जोड़ने की बात थी
      imperative दुनिया में भी यह debugging नहीं बल्कि decomposition की समस्या है — जैसे dependency injection, middleware install करना, public API boundaries पर concrete classes के बजाय abstract interfaces इस्तेमाल करना
      ऐसे design सुझाव refactoring होते हैं, और इनका असर debuggability से ज़्यादा इस पर पड़ता है कि observability middleware किसी और के public API पर कितनी आसानी से लगाया जा सकता है
  • Haskell की 20 लाख lines आखिर क्या कर रही होंगी, इसकी कल्पना करना मुश्किल है
    code सचमुच बहुत ज़्यादा है, और Haskell की छवि ऐसी “dense” भाषा की है जो कम code में बहुत काम कर देती है
    क्या इसकी वजह यह है कि JSON serialization/deserialization, REST API frameworks, logging जैसी चीज़ों के लिए बहुत सारी libraries हैं?

    • मूल लेख के अनुसार समस्या यह है कि जिस code को instrument नहीं किया जा सकता, उस पर भरोसा नहीं किया जा सकता
      अगर third-party bindings concrete functions के ज़रिए HTTP calls कर रही हैं, तो tracing जोड़ने का कोई तरीका नहीं, SLO के मुताबिक timeouts inject करने का कोई तरीका नहीं, tests में partner failures simulate करने का कोई तरीका नहीं, और trace में 400ms के खाली हिस्से को अटकलों के अलावा समझाने का भी कोई तरीका नहीं
      इसलिए उन्होंने खुद लिखा
      शुरुआत में काम ज़्यादा है, लेकिन अपने बनाए clients शुरू से ही ऐसे बनाए गए, इसलिए वे observability के लिए configured हैं
    • जिसे आपने “dense” कहा, उसे आम तौर पर high expressiveness कहा जाता है
      यानी अपेक्षाकृत बहुत abstract विचारों को कम characters में व्यक्त किया जा सकता है
      कुछ लोग इसे “high-level” भी कहते हैं
      फिर भी मुझे नहीं लगता कि 20 लाख lines उतनी ज़्यादा हैं जितनी पहली बार सुनने पर लगती हैं
      ख़ासकर अगर कंपनी finance जैसे regulated क्षेत्र में हो और code कई सालों में जमा हुआ हो, तो बिल्कुल नहीं
    • यह कोई objective metric नहीं है, लेकिन मुझे Haskell का aspect ratio ही अलग लगता था
      lines की संख्या कुछ हद तक कम हो सकती है, लेकिन शब्दों की संख्या ज़्यादा imperative object-oriented भाषाओं के लगभग बराबर रहती है
    • मुझे नहीं पता यह codebase वास्तव में कैसा है, लेकिन Haskell के concise होने की reputation कुछ हद तक academia और category theory की दुनिया के overrepresentation से आती है
      वहाँ St M -> C T जैसी अभिव्यक्तियाँ ठीक लगती हैं, लेकिन असली software में TransactionState Debit -> Verified Transaction जैसी चीज़ें कहीं ज़्यादा उपयोगी होती हैं
      दूसरा हिस्सा LISP तक पीछे जाने वाला cultural factor है
      लोग line count बचाने के लिए कठिन tricks या macros से ज़रूरत से ज़्यादा clever बनने की कोशिश करते हैं
      Mercury जैसी finance company में इस तरह की शैली की बजाय clarity और readability को बढ़ावा दिया जाता होगा
      उदाहरण के लिए linter शायद >> और >>= के साथ एक line में लिखने के बजाय monadic code को कई सावधानी से लिखी गई multi-line do expressions में बाँटने के लिए मजबूर करता हो