Algebraic Effects की जरूरत क्यों पड़ती है
(antelang.org)- Algebraic effects एक language feature है जो resumable exceptions की तरह control flow को पकड़कर handle करता है। यह Ante की core feature है और Koka, Effekt, Eff, Flix जैसी research languages में भी केंद्रीय रूप से इस्तेमाल होती है
- इसी mechanism से generators, exceptions, async, coroutines, automatic differentiation को library level पर बनाया जा सकता है, और effect polymorphism की वजह से
mapजैसे functions भी effect के प्रकार से स्वतंत्र होकर सिर्फ एक बार लिखे जा सकते हैं - database access, output, logging, state passing जैसी dependency injection और context passing को effects में बदलने पर test mocks, output collection, log filtering को handler बदलकर संभाला जा सकता है
- function signatures में
can IO,can Print,can Failजैसे effects दिखें तो purity guarantee, record/replay और security audit में फायदा होता है, लेकिन पहले से allowed effects अनजाने में मौजूदा handlers तक propagate हो सकते हैं - पारंपरिक कमजोरी efficiency concerns रही है, लेकिन हाल की languages tail-resumptive effect optimization, evidence passing, single
resumerestriction, handler specialization से cost घटा रही हैं
Algebraic effects का basic model
- Algebraic effects को effect handlers भी कहा जाता है, और इन्हें “resumable exceptions” वाले model से समझा जा सकता है
- Ante pseudocode में effect functions declare किए जाते हैं, और function signature में उस effect का इस्तेमाल कर सकने को
canसे दिखाया जाता हैsay_message: Unit -> Unitजैसे effect function को call करना effect को “throw” करने जैसा होता है- caller function
foo () can SayMessageकी तरह signature में उस effect के इस्तेमाल की संभावना दिखाता है
handleexpressiontry/catchजैसा effect को पकड़ता है, औरresumecall से रुकी हुई computation को आगे बढ़ाता है- अगर
say_messagehandlerprint "Hello World!"चलाने के बादresume ()call करे, तो original computation जारी रहती है और42return करती है
- अगर
- “algebraic” नाम ज्यादातर historical term है; वास्तव में effect handlers ज्यादा सटीक expression के करीब है, लेकिन users के लिए familiar नाम होने के कारण algebraic effects इस्तेमाल किया जाता है
Custom control flow
- Algebraic effects कई language features को एक ही mechanism से implement करने देते हैं
- generators
- exceptions
- async
- coroutines
- automatic differentiation
- Effect polymorphism what color is your function समस्या को कम करता है
map (input: Vec a) (f: a -> b can e): Vec b can eयह व्यक्त करता है कि input functionfजो भी effecteperform करे,mapभी वही effect perform करता है- वही
mapstdout output, async function calls, stream yield आदि के साथ इस्तेमाल किया जा सकता है - कई effect handler languages effect variable
eको omit करके familiarmap (input: Vec a) (f: a -> b): Vec bform में लिखने देती हैं
- Exceptions को effect handle करते समय
resumecall न करने के तरीके से implement किया जा सकता हैThrow aeffect काthrow: a -> never_returnsdefine किया जाता है- 0 से divide करने पर
throw "error: Division by zero!"call किया जाता है, और handler message print करने के बाद computation resume नहीं करता
- Generators को
Yield aeffect केyield: a -> Unitसे implement किया जा सकता है- vector elements पर iterate करते हुए
yield elemcall किया जाता है filterhandler, yielded value condition satisfy करे तो फिर सेyield xcall करता है औरresume ()से अगले element पर आगे बढ़ता हैmy_for_eachhandler हर yielded value के लिए functionfrun करता है औरresume ()से जारी रहता है
- vector elements पर iterate करते हुए
- Cooperative scheduler भी
yield: Unit -> Uniteffect से बनाया जा सकता है, जहां handler control लेकर किसी दूसरे function execution पर switch करता है- Effekt का scheduler example यह pattern दिखाता है
- कई effects आपस में अच्छी तरह compose होते हैं, और यह बात दूसरी effect abstractions की तुलना में usability बढ़ाने वाले advantage के रूप में देखी जाती है
Dependency injection और testability
- Effects को सामान्य business applications में भी dependency injection के लिए इस्तेमाल किया जा सकता है
- database object को function argument के रूप में सीधे pass करने के बजाय
Databaseeffect define किया जा सकता है- existing form
business_logic (db: Database) (x: I32)की तरह DB object को argument के रूप में लेता है - effect-based form
business_logic (x: I32) can Databaseबन जाता है, और अंदरquery "..."call करता है
- existing form
- concrete database का चुनाव call stack के ऊपर वाले handler के पास होता है
- production DB को किसी दूसरे DB से बदला जा सकता है या test के लिए mock DB से replace किया जा सकता है
mock_databasehandlerquerymessage को ignore करके हमेशाDbResponse.Okreturn करने के लिएresumeकर सकता है
- Output को भी effect की तरह treat करने पर test के दौरान stdout में सीधे लिखे बिना string के रूप में collect किया जा सकता है
print_to_stringhandlerprint msgcalls को पकड़करall_messagesstring में newline के साथ accumulate करता हैoutput_messagesactual output के बिना return value1234और message string verify कर सकता है
- Logging को
Logeffect औरLogLevelका इस्तेमाल करके conditional output में बदला जा सकता हैlog_handlermessage का level configured threshold से ऊपर या बराबर हो तोprint msgcall करता हैfoo () with log_handler Errorसिर्फ error logs output करता है
ज्यादा clean API और context passing
- Algebraic effects program या library के across pass होने वाले Context object pattern को effects से express कर सकते हैं
Use aeffect को state effect की तरह देखा जा सकता है, जोget: Unit -> a,set: a -> Unitprovide करता हैstatehandler initial state को store करता है,getपर current context return करता है, औरsetपर नए context से update करता है- example
statedefinition ownership rules को ignore करती है, और real implementation मेंCopy aconstraint की जरूरत हो सकती है
- vector के अंदर strings store करके index को key की तरह pass करने वाला example context passing की cost दिखाता है
- effect न इस्तेमाल करने पर
push_string,get_string,append_with_separator,exampleआदि कोstringsलगातार argument के रूप में लेना पड़ता है - effect-based implementation में primitive operations
push_string,get_stringget/setcall करते हैं, और higher-level code कोstringsसीधे pass नहीं करना पड़ता
- effect न इस्तेमाल करने पर
- यह तरीका तब अच्छा fit होता है जब library internal context passing को wrap करती है
- library users को context passing method की internal details की चिंता नहीं करनी पड़ती
- किसी खास context type से बंधना न हो तो needed functions को interface के रूप में abstract किया जा सकता है
Global variables का replacement और direct style
- random number generation या memory allocation जैसे APIs, जो बाहर से stateless लगते हैं लेकिन असल में state चाहिए, उन्हें global variables के बजाय effects से express किया जा सकता है
- random number generation example दिखाता है कि
Prngobject को पूरे program में सीधे pass करना कितना बोझिल है- global
Prngconvenient है, लेकिन thread safety की जरूरत जैसी global values की कमियां पैदा होती हैं Randomeffect केrandom: Unit -> U8का इस्तेमाल करने पर user को सिर्फ upper call stack में कहीं handler से initialization explicitly बताना होता है- बाद में
/dev/urandomया किसी दूसरे random source पर बदलना हो तो सिर्फ handler replace करना होगा, call stack का बाकी code बदलने की जरूरत नहीं
- global
- Memory allocation को भी
Allocateeffect से express किया जा सकता हैallocate: (size: Usz) -> Alignment -> Ptr afree: Ptr a -> Unit- ज्यादातर calls में global allocator इस्तेमाल करें, और tight loop के अंदर loop body में handler जोड़कर arena allocator में बदल सकते हैं
- Effects dedicated value में wrapped result pass करने के तरीके की तुलना में direct style संभव बनाते हैं
Maybe tइस्तेमाल करने पर success path कोand_then,mapसे chain करना पड़ता है- Rust का
?जैसा syntactic sugar good path पर focus करने का साधन है - effect-based
get_line_from_stdin (): String can Fail, IOऔरparse (s: String): U32 can Failको सामान्य sequential code की तरहline = ...,x = ...,x * 2से लिखा जाता है
- Failure handling को handler apply करके good path से बाहर जाने के तरीके के रूप में संभाला जा सकता है
get_line_from_stdin () with default "42"Faileffect को default value से handle करता है
- अलग-अलग error types भी effect list के रूप में स्वाभाविक रूप से compose होते हैं
LibraryA.foo (): U32 can Throw LibraryA.ErrorLibraryB.bar (): U32 can Throw LibraryB.Errormy_functionThrow LibraryA.Error,Throw LibraryB.Error,Throw MyErrorको साथ declare कर सकता है- repetition लंबा हो जाए तो
AllErrors = can Throw ...जैसा type alias बनाया जा सकता है - एक जैसा
Throw Stringeffect merge होकर एक हो जाता है, और अलग रखना हो तोMyErrorजैसा wrapper type चाहिए
Purity, re-executability और security audit
- अधिकांश effect handler languages, OCaml के आसपास को छोड़कर, side effects हो सकने वाली जगहों पर effects इस्तेमाल करती हैं
- Ante में
can Print,can IOजैसा mark न करें तो side effects इस्तेमाल नहीं कर सकते externdefinitions को compiler check नहीं कर सकता, इसलिए type definition पर भरोसा करना पड़ता है- सिर्फ debug mode में
IOeffect perform करके release mode की effect safety बनाए रखने का तरीका planned feature है
- Ante में
- कुछ functions input के रूप में pure function मांगते हैं
- thread create करते समय created thread को current thread के owned handlers में call नहीं कर पाना चाहिए
spawn_all (functions: Vec (Unit -> a pure)): Vec a can IOpure functions ही लेकर सभी functions को threads में run करता है और completion का wait करता है
- Software Transactional Memory(STM) ऐसी concurrency technique है जिसे pure functions चाहिए
- कई functions को simultaneously run करते समय transaction के दौरान value किसी दूसरे thread द्वारा बदली जाए तो उस transaction को फिर से start किया जाता है
- Effekt का proof-of-concept implementation effekt-stm में है
- Purity
rrdebugging utility जैसी record/replay possibility दे सकती हैrecordऔरreplayदो handlersmainद्वारा emitted top-level effects, आमतौर परIO, को handle करते हैंrecordeffect occurrence और result record करता है, और actual handling के लिए built-inIOhandler तक फिर raise करता हैreplayactualIOperform नहीं करता और effect log के results इस्तेमाल करता है- debug build में default रूप से record करें तो deterministic debugging मिल सकती है
- Function signature की effect list Capability Based Security जैसी security audit में मदद करती है
get_pi: Unit -> F64से पता चलता है कि यह background में चुपके सेIOनहीं करता- library update के बाद अगर
get_pi: Unit -> F64 can IOबन जाए, तो caller side function पहले सेIOrequire न करता हो तो code में error मिलेगा - न्यूनतम effects ही declare करना बेहतर है; जैसे पूरे
IOके बजाय सिर्फPrintdeclare करना अच्छा है - नया effect add करना semantic versioning तोड़ने वाला change माना जाता है
- related resources में Capability Based Security और Designing with Static Capabilities and Effects हैं
सीमाएं और implementation strategies
- Effect approach की एक सीमा यह है कि unintended handling संभव है
- कोई function नया
IOrequire करने लगे तब भी caller function पहले सेIOallow करता हो तो error नहीं आ सकता Faileffect भी इसी तरह है: पहले fail न होने वाला library function बाद मेंFailकर सके, तो यह existingFailhandler तक propagate हो सकता है- यह behavior परिस्थिति के अनुसार ठीक हो सकता है, लेकिन default value provide करने जैसे separate handling की इच्छा हो तो यह intention से अलग हो सकता है
- कोई function नया
- पारंपरिक मुख्य downside efficiency concerns रहा है, लेकिन हाल के effects का compiled output काफी बेहतर हुआ है
- कई algebraic effect languages tail-resumptive effects को सामान्य closure calls में optimize करती हैं
- tail-resumptive effect वह effect है जिसमें handler अंत में
resumecall करता है - वास्तविक effects में से अधिकांश इसी category में आते हैं, और main text के ज्यादातर examples भी इसी category में हैं
- exception को exceptional case माना जाता है क्योंकि वह
resumeबिल्कुल call नहीं करता
- tail-resumptive effect वह effect है जिसमें handler अंत में
- language-specific optimization strategies भी अलग हैं
- Koka evidence passing इस्तेमाल करता है और effects को handler तक hoist करके runtime के बिना C में compile करता है
- Ante और OCaml
resumeको ज्यादा से ज्यादा एक बार call करने की restriction लगाते हैं- यह restriction nondeterminism जैसे कुछ effects को exclude करती है
- इसके बदले resource handling सरल होता है और segmented stacks जैसे तरीकों से internal continuation को ज्यादा efficiently implement किया जा सकता है
- Effekt handlers को program से पूरी तरह specialize करके remove करता है
- इस approach में ज्यादातर functions को second-class बनाने की limitation है
- boxed form में first-class functions प्राप्त किए जा सकते हैं और pay-as-you-go तरीके से switch किया जा सकता है
- related resources में Effekt captures docs और paper हैं
अभी कोई टिप्पणी नहीं है.