• 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 resume restriction, 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 के इस्तेमाल की संभावना दिखाता है
  • handle expression try/catch जैसा effect को पकड़ता है, और resume call से रुकी हुई computation को आगे बढ़ाता है
    • अगर say_message handler print "Hello World!" चलाने के बाद resume () call करे, तो original computation जारी रहती है और 42 return करती है
  • “algebraic” नाम ज्यादातर historical term है; वास्तव में effect handlers ज्यादा सटीक expression के करीब है, लेकिन users के लिए familiar नाम होने के कारण algebraic effects इस्तेमाल किया जाता है

Custom control flow

  • Algebraic effects कई language features को एक ही mechanism से implement करने देते हैं
  • Effect polymorphism what color is your function समस्या को कम करता है
    • map (input: Vec a) (f: a -> b can e): Vec b can e यह व्यक्त करता है कि input function f जो भी effect e perform करे, map भी वही effect perform करता है
    • वही map stdout output, async function calls, stream yield आदि के साथ इस्तेमाल किया जा सकता है
    • कई effect handler languages effect variable e को omit करके familiar map (input: Vec a) (f: a -> b): Vec b form में लिखने देती हैं
  • Exceptions को effect handle करते समय resume call न करने के तरीके से implement किया जा सकता है
    • Throw a effect का throw: a -> never_returns define किया जाता है
    • 0 से divide करने पर throw "error: Division by zero!" call किया जाता है, और handler message print करने के बाद computation resume नहीं करता
  • Generators को Yield a effect के yield: a -> Unit से implement किया जा सकता है
    • vector elements पर iterate करते हुए yield elem call किया जाता है
    • filter handler, yielded value condition satisfy करे तो फिर से yield x call करता है और resume () से अगले element पर आगे बढ़ता है
    • my_for_each handler हर yielded value के लिए function f run करता है और resume () से जारी रहता है
  • Cooperative scheduler भी yield: Unit -> Unit effect से बनाया जा सकता है, जहां handler control लेकर किसी दूसरे function execution पर switch करता है
  • कई effects आपस में अच्छी तरह compose होते हैं, और यह बात दूसरी effect abstractions की तुलना में usability बढ़ाने वाले advantage के रूप में देखी जाती है

Dependency injection और testability

  • Effects को सामान्य business applications में भी dependency injection के लिए इस्तेमाल किया जा सकता है
  • database object को function argument के रूप में सीधे pass करने के बजाय Database effect define किया जा सकता है
    • existing form business_logic (db: Database) (x: I32) की तरह DB object को argument के रूप में लेता है
    • effect-based form business_logic (x: I32) can Database बन जाता है, और अंदर query "..." call करता है
  • concrete database का चुनाव call stack के ऊपर वाले handler के पास होता है
    • production DB को किसी दूसरे DB से बदला जा सकता है या test के लिए mock DB से replace किया जा सकता है
    • mock_database handler query message को ignore करके हमेशा DbResponse.Ok return करने के लिए resume कर सकता है
  • Output को भी effect की तरह treat करने पर test के दौरान stdout में सीधे लिखे बिना string के रूप में collect किया जा सकता है
    • print_to_string handler print msg calls को पकड़कर all_messages string में newline के साथ accumulate करता है
    • output_messages actual output के बिना return value 1234 और message string verify कर सकता है
  • Logging को Log effect और LogLevel का इस्तेमाल करके conditional output में बदला जा सकता है
    • log_handler message का level configured threshold से ऊपर या बराबर हो तो print msg call करता है
    • foo () with log_handler Error सिर्फ error logs output करता है

ज्यादा clean API और context passing

  • Algebraic effects program या library के across pass होने वाले Context object pattern को effects से express कर सकते हैं
  • Use a effect को state effect की तरह देखा जा सकता है, जो get: Unit -> a, set: a -> Unit provide करता है
    • state handler initial state को store करता है, get पर current context return करता है, और set पर नए context से update करता है
    • example state definition ownership rules को ignore करती है, और real implementation में Copy a constraint की जरूरत हो सकती है
  • 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_string get/set call करते हैं, और higher-level code को strings सीधे pass नहीं करना पड़ता
  • यह तरीका तब अच्छा 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 दिखाता है कि Prng object को पूरे program में सीधे pass करना कितना बोझिल है
    • global Prng convenient है, लेकिन thread safety की जरूरत जैसी global values की कमियां पैदा होती हैं
    • Random effect के random: Unit -> U8 का इस्तेमाल करने पर user को सिर्फ upper call stack में कहीं handler से initialization explicitly बताना होता है
    • बाद में /dev/urandom या किसी दूसरे random source पर बदलना हो तो सिर्फ handler replace करना होगा, call stack का बाकी code बदलने की जरूरत नहीं
  • Memory allocation को भी Allocate effect से express किया जा सकता है
    • allocate: (size: Usz) -> Alignment -> Ptr a
    • free: 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" Fail effect को default value से handle करता है
  • अलग-अलग error types भी effect list के रूप में स्वाभाविक रूप से compose होते हैं
    • LibraryA.foo (): U32 can Throw LibraryA.Error
    • LibraryB.bar (): U32 can Throw LibraryB.Error
    • my_function Throw LibraryA.Error, Throw LibraryB.Error, Throw MyError को साथ declare कर सकता है
    • repetition लंबा हो जाए तो AllErrors = can Throw ... जैसा type alias बनाया जा सकता है
    • एक जैसा Throw String effect merge होकर एक हो जाता है, और अलग रखना हो तो MyError जैसा wrapper type चाहिए

Purity, re-executability और security audit

  • अधिकांश effect handler languages, OCaml के आसपास को छोड़कर, side effects हो सकने वाली जगहों पर effects इस्तेमाल करती हैं
    • Ante में can Print, can IO जैसा mark न करें तो side effects इस्तेमाल नहीं कर सकते
    • extern definitions को compiler check नहीं कर सकता, इसलिए type definition पर भरोसा करना पड़ता है
    • सिर्फ debug mode में IO effect perform करके release mode की effect safety बनाए रखने का तरीका planned feature है
  • कुछ functions input के रूप में pure function मांगते हैं
    • thread create करते समय created thread को current thread के owned handlers में call नहीं कर पाना चाहिए
    • spawn_all (functions: Vec (Unit -> a pure)): Vec a can IO pure 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 rr debugging utility जैसी record/replay possibility दे सकती है
    • record और replay दो handlers main द्वारा emitted top-level effects, आमतौर पर IO, को handle करते हैं
    • record effect occurrence और result record करता है, और actual handling के लिए built-in IO handler तक फिर raise करता है
    • replay actual IO perform नहीं करता और 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 पहले से IO require न करता हो तो code में error मिलेगा
    • न्यूनतम effects ही declare करना बेहतर है; जैसे पूरे IO के बजाय सिर्फ Print declare करना अच्छा है
    • नया effect add करना semantic versioning तोड़ने वाला change माना जाता है
    • related resources में Capability Based Security और Designing with Static Capabilities and Effects हैं

सीमाएं और implementation strategies

  • Effect approach की एक सीमा यह है कि unintended handling संभव है
    • कोई function नया IO require करने लगे तब भी caller function पहले से IO allow करता हो तो error नहीं आ सकता
    • Fail effect भी इसी तरह है: पहले fail न होने वाला library function बाद में Fail कर सके, तो यह existing Fail handler तक propagate हो सकता है
    • यह behavior परिस्थिति के अनुसार ठीक हो सकता है, लेकिन default value provide करने जैसे separate handling की इच्छा हो तो यह intention से अलग हो सकता है
  • पारंपरिक मुख्य downside efficiency concerns रहा है, लेकिन हाल के effects का compiled output काफी बेहतर हुआ है
  • कई algebraic effect languages tail-resumptive effects को सामान्य closure calls में optimize करती हैं
    • tail-resumptive effect वह effect है जिसमें handler अंत में resume call करता है
    • वास्तविक effects में से अधिकांश इसी category में आते हैं, और main text के ज्यादातर examples भी इसी category में हैं
    • exception को exceptional case माना जाता है क्योंकि वह resume बिल्कुल call नहीं करता
  • 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 हैं

अभी कोई टिप्पणी नहीं है.

अभी कोई टिप्पणी नहीं है.