Railway Oriented Programming
(fsharpforfunandprofit.com)- Railway Oriented Programming एक ऐसा approach है जो validation, logging और network errors जैसे real-world applications में बार-बार आने वाले failure paths को functional pipeline के भीतर संभालता है
- सिर्फ success path को जोड़ने वाले examples की सीमा को railway analogy से समझाना इसका मुख्य विचार है, ताकि F# users भी error flow को visually आसानी से समझ सकें
- Haskell के नजरिए से यह
Eithertype और Kleisli composition के करीब है, लेकिन abstract theory की बजाय error-handling recipes और implementation patterns पर focus करता है - Practical use के लिए NuGet के लिए F# library Chessie, C#·F# comparison GitHub examples, और FizzBuzz application example साथ में दिए गए हैं
- यह उपयोगी तरीका है, लेकिन हर problem पर इसे जरूरत से ज्यादा लागू नहीं करना चाहिए; F# में type class नहीं है, इसलिए example library जरूरी functions को खुद define करती है
Presentation materials और example code
- Railway Oriented Programming page presentation slides और code को एक जगह इकट्ठा करने वाला resource है
- Functional programming examples अक्सर जिस “happy path” को assume करते हैं, सिर्फ उससे real-world applications बनाना मुश्किल है—यहीं से यह चर्चा शुरू होती है
- Robust applications को validation, logging, network errors, service errors और अन्य exceptional situations को भी साथ में handle करना पड़ता है
- लक्ष्य यह है कि ऐसी error handling को clean functional तरीके से कैसे संभालें, इसे railway analogy के जरिए छोटा और समझने में आसान बनाकर introduce किया जाए
- मिलता-जुलता topic recipe for a functional app series में भी आगे जारी रहता है
- Actual code C# और F# के ROP approaches की तुलना करने वाले GitHub project में देखा जा सकता है
- Error handling के लिए उपयोगी होने के बावजूद इसे extreme तरीके से apply न करने की चेतावनी के साथ “Against Railway-Oriented Programming” भी reference के तौर पर दिया गया है
Video और slides
- यह topic NDC London 2014 में present किया गया था
- उसी presentation के दूसरे videos भी public हैं
- Functional Programming eXchange में 14 मार्च 2014 की presentation slides उपलब्ध हैं
- PowerPoint slides GitHub से भी डाउनलोड की जा सकती हैं, और बताया गया है कि उन्हें freely borrow करके use किया जा सकता है
Either monad और Kleisli composition
- Haskell users को यह approach
Eithertype जैसा लग सकता है - यहां इसे
Leftcase में custom error types की list इस्तेमाल करने वाले रूप में specialize किया गया है- Example type है
type TwoTrack a b = Either [a] (b,[a])
- Example type है
- नया बनाया गया हिस्सा error handling concept खुद नहीं, बल्कि उसे समझाने वाली railway analogy के ज्यादा करीब है
- Standard Haskell terminology को आगे न रखने की वजह साफ है
- यह material monad tutorial नहीं है, बल्कि error-handling problem को solve करने पर केंद्रित है
- F# से परिचित होने वाले कई लोग monads से familiar नहीं होते, इसलिए visual और कम डराने वाली explanation ज्यादा intuitive हो सकती है
bindवाले two-track type को सीधे monad कहना accurate नहीं है, और monad laws तक चर्चा नहीं जातीEitherबहुत general tool है, इसलिए यहां tool itself की बजाय recipe देने की कोशिश है
Error-handling recipe के components
- यह approach “बस
Eitherऔरbindइस्तेमाल कर लो” से ज्यादा comprehensive techniques का bundle है - इसमें शामिल techniques ये हैं
Either String aजैसे simple form के बजाय, left और right दोनों sides में custom error types की list का उपयोगbind(>>=) से monadic functions को pipeline में integrate करना- Kleisli composition(
>=>) से monadic functions compose करना map(fmap) से non-monadic functions को pipeline में integrate करना- F# IO monad use नहीं करता, इसलिए
teeसे unit functions को pipeline में integrate करना - Exceptions को error cases में map करना
- Validation जैसी situations में
&&&से monadic functions को parallel combine करना - Domain-driven design में custom error types से मिलने वाले benefits का उपयोग
- Logging, domain events और compensation transactions जैसे extensions
- लक्ष्य एक ऐसा template बनाना है जो लगभग हर situation में use होने जितना versatile हो, फिर भी consistent style enforce करने जितना restrictive हो
- जब code लिखने का तरीका practically एक ही दिशा में converge करता है, तो बाद में maintain करने वाला व्यक्ति code structure को जल्दी समझ सकता है
- यह तरीका एकमात्र solution नहीं है, लेकिन अच्छी starting point हो सकता है
- Haskell community के भीतर भी error handling approaches consistent नहीं हैं, इसलिए beginners के लिए यह confusing हो सकता है
अपने code में apply करने का तरीका
- अगर तैयार F# library चाहिए, तो NuGet के साथ use की जा सकने वाली Chessie project देखी जा सकती है
- इस technique को apply करने वाली sample web service GitHub project के रूप में दी गई है
- ROP approach को FizzBuzz पर apply करने वाला example Railway Oriented Programming: carbonated में देखा जा सकता है
- F# में type class नहीं है, इसलिए monads को reuse करने का कोई general तरीका नहीं है
- FSharpX library useful approach देती है, लेकिन
Rop.fslibrary सभी functions को शुरुआत से खुद define करती है - इस तरह isolate करने का फायदा यह है कि कोई external dependency बिल्कुल नहीं रहती
आगे पढ़ें
- Monad को सही तरीके से define करने के लिए सिर्फ
bindऔरreturnimplement करना काफी नहीं है - Monad एक algebraic structure है जिसे monad laws satisfy करने होते हैं, और ये laws कुछ specific situations में monoid laws से related होते हैं
- अगर
Eitherऔर Kleisli composition में interest हो, तो ये resources देखे जा सकते हैं- Monads in general
- The
Eithermonad - Kleisli categories and composition
- Comprehensive error handling approaches
2 टिप्पणियां
दोष-रहित प्रोग्राम बनाने के लिए Railway-Oriented Programming
Hacker News की राय
Elixir इस समस्या को
withkeyword/macro से काफ़ी साफ़ तरीके से संभालता हैजैसे
with {:ok, file_handle} <- File.open(filename), ... do {:ok, parsed} endमें functions ऊपर से नीचे execute होते हैं, और अगर हर return value बाईं ओर के pattern से match नहीं करती, तो उसी value के साथ early return हो जाता हैइसलिए हर function को
{:ok, value}और{:error, reason}दोनों लेने के लिए बनाने की ज़रूरत नहीं पड़ती; function सिर्फ़ अपनी रुचि वाली value ले सकता है औरwithblock की pattern matching को error propagation संभालने दिया जा सकता हैउदाहरण के लिए, अगर
File.open{:error, reason}return करता है, तोIO.readexecute नहीं होगा और पूरेwithका result{:error, reason}बन जाएगा. आखिर में आप सिर्फ़ success path लिखते हैं और failure path को caller चाहे तो match कर सकता हैयह exception handling को आधा फिर से invent करने जैसा लगता है, और
catchकी जगहelseजैसा नाम दे दें तो बस यह exception handling जैसा कम दिखता हैwhereऔर Lisp केletसे प्रभावित रूप हैयह function की preconditions set करने जैसा है, और Elixir में इसके अलावा
elseblock भी इस्तेमाल कर सकते हैंIO.readfail हो जाए, तो file handle कौन close करेगा, यह भी समस्या हैलेखक ने कुछ साल बाद Against Railway Oriented Programming नाम से एक follow-up पोस्ट भी लिखी: https://fsharpforfunandprofit.com/posts/against-railway-orie...
Railway Oriented Programming दिलचस्प है और इसके उपयोग की जगहें भी हैं, लेकिन मुझे लगता है कि इसके साथ बड़ी सावधानी वाली चेतावनी चाहिए। व्यवहार में मैंने इसे अक्सर exception handling को खराब तरीके से फिर से गढ़ने में इस्तेमाल होते देखा है, और exceptions को ठीक से समझकर इस्तेमाल किया जाए तो ज़्यादातर error conditions को ज़्यादा साफ़ और असरदार तरीके से संभाला जा सकता है
exceptions का फायदा यह है कि ज़्यादातर मामलों में सुरक्षित विकल्प default होता है। error condition इस बात का संकेत है कि code specification के मुताबिक काम नहीं कर सकता, और उस स्थिति में आगे बढ़ते रहने से गलत assumptions के कारण data corrupt हो सकता है। exceptions default रूप से cleanup के बाद caller तक propagate होते हैं, लेकिन Railway Oriented Programming जैसी शैली में बहुत सा boilerplate चाहिए होता है जिसे भूलना और गलत करना आसान है
हालांकि दो मामलों में यह उपयोगी है। एक, validation जैसी स्थिति जहां किसी specific और well-defined expected error को उसी जगह handle करना हो जहां वह पैदा हुआ है; दूसरा, ऐसा environment जहां exception handling इस्तेमाल नहीं की जा सकती। पुराने jQuery का Promise-based asynchronous code लगभग Railway Oriented Programming की implementation जैसा था, लेकिन आधुनिक JavaScript के
async/awaitमें फिर से exception handling इस्तेमाल की जा सकती हैRailway Oriented Programming का बड़ा फायदा यह है कि function signature देखकर ही तुरंत पता चल जाता है कि कौन-सी errors आ सकती हैं
boilerplate को भाषा के हिसाब से कम किया जा सकता है। Haskell में
donotation है, और F# में result computation expression से इसे बहुत साफ़ तरीके से लिखा जा सकता है। उदाहरण के लिएLoginError = InvalidUser | InvalidPwd | Unauthorized of AuthErrorकी तरह define करके,Result.requireSome,Result.requireTrue,Result.mapErrorसे flow बनाया जा सकता हैइसके उलट, मैं जानना चाहता हूं कि क्या exceptions की कमियां भी कम की जा सकती हैं। क्या Java IDE के साथ integrate होने वाला कोई linter या static analysis tool है जो किसी specific code line से throw हो सकने वाले uncaught exceptions को अपने-आप दिखा दे
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoo...
सबसे पहले, कई languages exceptions पकड़ने के लिए अटपटा और अनावश्यक scope मजबूर करती हैं। जैसे, exception throw कर सकने वाले function के result से variable declare/initialize करना हो, और failure पर कोई दूसरा value डालना हो, तो declaration और initialization अलग करने पड़ते हैं
बड़ा मुद्दा यह है कि error conditions को उस सबसे निचले level पर handle करना चाहिए जहां उन्हें सही तरीके से handle करने के लिए पर्याप्त context हो, जबकि exceptions का default “सबसे ऊपर तक throw करना” है। जैसे, सिर्फ़ एक file cache जोड़ने से ऊपर का
HandleRequestfunction अचानक I/O exception throw करने में सक्षम हो जाता है, और abstraction leak होना आसान हैमेरा मानना है कि function जो कुछ भी return कर सकता है, वह सब explicitly signature का हिस्सा होना चाहिए, और caller को या तो explicitly handle करना चाहिए या ऊपर pass करने का संकेत देना चाहिए। language को इसके लिए बहुत boilerplate मांगने की ज़रूरत नहीं है
Resulttype/trait ने error handling को सही तरह से कर दिखाया हैfile या record न होने पर भी यह expected है या नहीं, इस पर निर्भर करता है। अगर user द्वारा दिए गए ID से lookup करने वाली request है, तो वह lookup खुद user input validation है। लेकिन अगर database record में stored blob name से storage lookup किया और blob नहीं मिला, तो यह exception के रूप में handle करने लायक स्थिति है
errors और exceptions की सीमा धुंधली है और team व problem domain के अनुसार बदलती है, लेकिन मुझे यह ठीक-ठाक rule of thumb लगता है
C++ code में शुरुआत में data की जांच करना, जांच fail होने पर application को crash कर देना, और मिले हुए error state के आधार पर शुरुआती जांच code को debug/fix करना जैसे तरीके से handle किया जा सकता है
जिस data से काम है वह ज्ञात CAD data format या geometric topology के अनुसार अपेक्षाकृत स्पष्ट data है, इसलिए पहले यह समझ पाना कि valid data क्या है, error conditions संभालने को “काफी आसान” बनाता है
सच कहूं तो यह पूरा site ही खज़ाना है। functional language इस्तेमाल करने का इरादा न हो तब भी यह एक अलग नज़रिया देता है, और व्यक्तिगत रूप से इससे मुझे बहुत मदद मिली। बाकी posts भी recommend करता हूं
फिर भी मुझे लगता है कि monad क्या है, यह मैं आखिर तक नहीं समझ पाऊंगा
https://fsharpforfunandprofit.com/series/understanding-parse...
मुझे जानना है कि “error handling को call site से दूर धकेलना खराब है” वाला दावा सही बैठता है या नहीं। मुझे लगता है कि caller ही error को सबसे अच्छे तरीके से handle कर सकता है, तो उसे नीचे पास करने के बजाय सीधे handle करना चाहिए
उदाहरण के लिए
validate and-then update-db and-then send-emailflow मेंvalidatefailure को caller को लौटाना ठीक हो सकता है। लेकिन अगरupdate-dbfail हो जाए, तो तय करना होगा कि retry करना है, कोई दूसरी service इस्तेमाल करनी है, queue में फिर डालना है, या user को बताना है।send-emailfailure भी ऐसा ही हैआखिर में लगता है कि structure कहीं ज़्यादा complex branching वाला हो जाएगा:
ValidationErrorपर user को notify करना,DBErrorपर 5 मिनट बाद retry करना या remote retry के बाद notify करना, औरEmailFailedपर email को फिर से queue में डालकर success मान लेना। शायद यह भी ठीक हो सकता हैGo को नापसंद करने वाले लोग शिकायत करते हैं कि error conditions बहुत बार check करनी पड़ती हैं, और Java के checked exceptions भी कुछ ऐसे ही हैं।
Eitherइस्तेमाल करने पर check करने के लिए मजबूर नहीं किया जाता; आप खुद check कर सकते हैं या caller को आगे पास कर सकते हैंJava के unchecked exceptions को नापसंद करने वाले लोग शिकायत करते हैं कि पता नहीं चलता क्या कब throw होगा।
Eitherइस्तेमाल करने पर यह explicit हो जाता हैअसली बात यह है कि error होने पर क्या करना है, यह हमें नहीं पता होता, और
Eitherइस अनिश्चितता के लिए अच्छी तरह fit बैठता है। मौजूदा Java codebase मेंLimit getUserLimit(User)न मिलने परNotFoundexception throw कर सकता है,nullreturn कर सकता है, या default value return कर सकता है; code में गहराई से देखे बिना यह पता नहीं चलताअगर
Eitherहोता, तो caller internal code पढ़े बिना भरोसा कर सकता था, औरorElse(null)याorElseThrow(...)जैसे built-in tools से उसे आसानी से किसी दूसरी failure condition में बदल सकता था। साथ हीEitherएक expression है, इसलिए abstraction के लिए अच्छा है;DbUpdaterके अंदर specialized retry code रखने के बजाय general retry logic लिखा जा सकता हैtry-catchstyle में implement करके देखा, लेकिन nestedtry,catch(DBError),sleep(5), remote retry,EmailFailedErrorrequeueing, औरValidationErrornotification आपस में उलझकर कहीं ज़्यादा खराब दिखते हैंhttps://gist.github.com/Andrewp2/9d97bd213b061166d6df565ce26...
कोई शायद बेहतर
try-catchversion लिख सकता है, लेकिन मुझे लगता है कि यह दिए गए railway-style version से काफी ज्यादा खराब हैDDD मुझे पसंद है, लेकिन मेरे जैसे साधारण programmer के लिए maintainable और reasonable maintenance cycle चाहिए। mutable code में TDD अच्छा इसलिए था क्योंकि उसने maintenance की cognitive load घटाई थी
railway-style programming कई transformations से गुजरते हुए काफी context ढो सकती है, और track के end पर ऐसी train पाने की स्थिति से बचना चाहूंगा जिसमें बहुत सारा business और system context लदा हो जिसके लिए वह तैयार नहीं है
मुझे लगता है DDD और onion architecture दोनों तरफ़ के फायदे देते हैं। आखिरकार railway बनती है, लेकिन वह micro app के अंदर धंसी हुई नहीं होती, बल्कि बाहर होती है। railway-style issues को explicit और थोड़ा बोझिल तरीके से code करना बेहतर है, और अगर काम अच्छा किया गया हो तो ज़्यादातर railway-style decisions ऐसी business decisions होती हैं जिन्हें clever coding structure से avoid नहीं करना चाहिए
monadic programming बेहतरीन है, लेकिन इसे जरूरी business decisions से बचने के लिए इस्तेमाल करने का temptation होता है। ambiguity को type system में डालने से आगे चलकर मुश्किल या असंभव हालात बन सकते हैं
try/catchके अंदर specializedtry/catchहोने से अलग नहीं हैउदाहरण के लिए
send-emailfunction के अंदर आप बहुत तेज़ transient errors को handle करना चुन सकते हैं। अगर allowed time के भीतर connection हो जाए तो success path पर वापस आ जाए, नहीं तो caller को return कर देअगर कोई पक्का rule है, तो वह यह है कि जिस handling में human intervention चाहिए, उसे जरूर wrap करके ऊपर पास करना चाहिए। success path पर वापस आ जाए तब भी warning log छोड़ना बेहतर है
notify-userValidationErrorऔरDBErrorके हिसाब से user notifications संभाले,try-update-dbupdate-db, 5 मिनट बाद retry, और remote retry को bundle करे, औरtry-send-emailfailure होने पर email को फिर से queue में डालकर उसे success में बदल देतब top-level
validate and-then try-update-db and-then try-send-email or-then notify-userजैसा बना रहेगा। लगता हैand/or/thenकोmap/bindके हिसाब से ठीक से translate किया गया हैयह site practical और उपयोगी concepts सिखाने के मामले में मेरे देखे हुए programming education sites में सबसे अच्छी थी। खासकर गलत states को represent करना असंभव बनाना वाला concept मुझे पसंद है, और मैं इसे भाषा की परवाह किए बिना apply करने की कोशिश करता हूं। बेशक कुछ भाषाएं इसे आसान बना देती हैं
https://fsharpforfunandprofit.com/posts/designing-with-types...
call/return के बजाय डेटा flow इस्तेमाल करने पर यह समस्या बेहतर ढंग से हल होती है, और काफी हद तक अपने-आप गायब हो जाती है
call/return में कुछ न कुछ return करना पड़ता है, इसलिए अगर error की वजह से return करने के लिए कोई value न हो तो error return करना पड़ता है, या Go की तरह error और normal return value दोनों साथ में return करने पड़ते हैं। इससे success path दूषित हो जाता है। polymorphic container से बाकी processing को पिरोकर आगे बढ़ाने पर थोड़ा सुधार होता है, लेकिन समस्या बनी रहती है
डेटा flow में अगले filter step को कुछ भी न भेजना काफी है, इसलिए success path पर बिल्कुल असर नहीं पड़ता। error को standard error output जैसी जगह भेजा जा सकता है, और application level पर centralize किया जा सकता है
यह बहुत अच्छा लग सकता है, लेकिन Wunderlist में इसे सचमुच इस्तेमाल किया गया था और यह अच्छी तरह काम करता था। जब value न हो तब इस्तेमाल की जाने वाली वही technique उस समय भी ज्यों-की-त्यों लागू होती है जब value अभी न हो, यानी asynchronous processing में
किसी simple data stream में error आए तो अगले step के लिए data नहीं है, इसलिए रुक जाना ठीक है। लेकिन अगर
sqrt(-1)पर तुरंत “crash” करके error output या logging पर कूद जाए, तो कुछ खास use cases में negative numbers के लिए कोई अलग value देने हेतु domain को फैलाना चाहें तो मुश्किल हो जाती हैऐसे में
Maybeजैसा explicit error type उपयोगी होता है। success path को ऐसे लिखें जैसे कोई error नहीं है, लेकिन function composition के लिए अलग operator इस्तेमाल करें। अगर error से recover करना हो तो उसी जगह दोनों paths को handle करें, और उसके बाद error की अनुमति न भी दें। abstract “हो भी सकने वाली value” से actual value निकालने के लिए दोनों paths को सलीके से handle करना पड़ता है—यह बात खास तौर पर अच्छी हैexplicit error type इस्तेमाल करने पर success/error की द्विआधारी सोच से भी बाहर निकला जा सकता है। “value नहीं है, लेकिन”, “value है, लेकिन” जैसी states भी व्यक्त की जा सकती हैं, और parallel में process किए जा सकने वाले कई tasks में errors को accumulate करके result के रूप में जुटाया जा सकता है। इससे आगे, nondeterminism को encode करके graph traversal जैसी चीजों में भी इस्तेमाल किया जा सकता है
Haskell programmers की तरह state को pure तरीके से carry भी किया जा सकता है, और कुख्यात Haskell का
IOmonad असल path को व्यक्त करने से ज्यादा बाहरी दुनिया से संपर्क के points कोmainतक सीमित करने वाला mechanism है। यह purity के लिए है, लेकिन मेरे हिसाब से purity हमेशा absolute रूप से पीछा किए जाने लायक value नहीं हैयह technique simple railway से कहीं ज्यादा विविध है, और purity बनाए रखते हुए सिर्फ success path लिखने का साफ-सुथरा तरीका देती है। equation-based static analysis और mock-less unit tests के लिए भी फायदेमंद है। काश और mainstream languages ऐसा monad toolbox बेहतर ढंग से उपलब्ध कराएं
संबंधित links ये हैं: Railway Oriented Programming - https://news.ycombinator.com/item?id=31404643 - मई 2022, Railway-Oriented Programming (2015) - https://news.ycombinator.com/item?id=17337155 - जून 2018, Railway oriented programming - https://news.ycombinator.com/item?id=11955917 - जून 2016, Railway Oriented Programming - https://news.ycombinator.com/item?id=9166943 - मार्च 2015, Railway-oriented programming - https://news.ycombinator.com/item?id=7887134 - जून 2014
इसके अलावा What is railway oriented programming? (2020) - https://news.ycombinator.com/item?id=34245639 - जनवरी 2023, Railway Oriented Programming in Elixir (2015) - https://news.ycombinator.com/item?id=11958578 - जून 2016
यह style दूसरे monads की तरह संक्रामक है, इसलिए सारा business logic इसी आकार का हो जाता है। अगर यह आपको ठीक लगे तो आगे पढ़ सकते हैं
exceptions, नाम के मुताबिक, unexpected errors हैं, और जहां catch किया जाता है वहां तक stack को unwind करते हुए जरूरी cleanup अपने-आप हो जाता है। ये I/O error या out of memory जैसी उन errors के लिए उपयुक्त हैं जो business function के scope के बाहर पैदा होती हैं
अगर data validate करना हो तो exception handler या monad पर निर्भर होने की जरूरत नहीं। functional pipeline के result को data बना दें। validation errors हों तो उन्हें set के रूप में इकट्ठा करें, और pipeline खत्म होने के बाद check करें। आम तौर पर पहली error पर fail करना शायद आप नहीं चाहेंगे
अगर pipeline में side effect चाहिए, तो ऐसा न करें; उस effect को data के रूप में describe करें और बाद में execute करें। बीच में जल्दी बाहर निकलना हो तो उसी point पर pipeline को split करें, result को process करें और फिर किसी दूसरी pipeline में डाल दें
या interceptor chain इस्तेमाल करके यह तय करवाया जा सकता है कि success path जारी रखना है या नहीं। language support करे तो pattern matching इस्तेमाल करें। अहम बात यह है कि pipeline के किसी हिस्से को skip करना है या नहीं, इसका फैसला business function के बाहर होता है; तब business function सिर्फ pure data transformation पर ध्यान देता है, जिससे उसकी reusability बढ़ती है
ऐसे विषय का फिर से सामने आकर चर्चा में आना ही अच्छी बात है। मैंने इस साइट को 1 साल तक पढ़ा और उदाहरणों को follow किया, और यह सबसे अच्छे F# resources में से एक था
इस साइट में बताए गए concepts भर से ही किसी भी language में functional सोच रखने में मदद मिली
यह पुरानी साइट है, लेकिन अगर अब फिर ध्यान पा रही है तो सोचता हूँ कि क्या इसका मतलब है कि F# momentum पकड़ रहा है। और यह भी जानना चाहता हूँ कि क्या दूसरी languages के programmers भी Rust की तरह railway-style error handling इस्तेमाल करते हैं
फिर भी F# पर switch न कर पाने की वजह यह है कि मेरा ज़्यादातर समय desktop frontend को adjust करने में जाता है। इस क्षेत्र में F# कोई खास फायदा देता हुआ नहीं लगता। Production-ready desktop frontend native F# नहीं, बल्कि ज़्यादातर C#-based object-oriented code होता है, और F# से बस पतले glue code के जरिए जुड़ता है
जैसे ही थोड़ा भी complex UI customization चाहिए होता है, फिर object-oriented दुनिया में लौटना पड़ता है, और ऐसा है तो मेरे हिसाब से सीधे C# में करना बेहतर है। निजी तौर पर, F# को momentum पकड़ना है तो उसे native UI framework चाहिए
Tऔर result condition define करने वाले किसी specific enum से parameterize किया जा सकेजल्दी में हो तो
std::pairसे भी कर सकते हैं, और error message को string में रखकर return करना, फिर string खाली न हो तो early exit करना भी संभव है। अगर हर function ऐसा type return करे, तो error message को top level तक bubble up किया जा सकता हैमुझे अच्छा लगता है कि यह लेख हर कुछ साल में फिर सामने आ जाता है। यह सचमुच बेहतरीन लेख और presentation है
हर बार दोबारा पढ़ते समय मैं अपनी technical journey में skill या philosophy के लिहाज से अलग जगह पर होता हूँ, इसलिए हर बार यह अलग दिखता है—यह भी दिलचस्प है