Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to algebraic effects

How to algebraic effects

Nikita Shilnikov

November 13, 2020
Tweet

More Decks by Nikita Shilnikov

Other Decks in Programming

Transcript

  1. 2018 — React Hooks Brought to us by 1 person

    more or less* * Sebas(an Markbåge 13/130
  2. Algebraic effects generalize all sorts of things —Dependency injec,on —Context

    passing —Excep,ons —Caching —Retry strategies —Non-determinism (A/B tes,ng, randomness) —Mul%threading —Scheduling (async/ await) —Generators —Timeouts —Configura%on —Locking 14/130
  3. Handler example (seman.cs) def run_greeting begin greeting when Get(what), resume

    then resume.("RubyRussia") return =" message message.center(21, "-") end end 32/130
  4. Handler def run_greeting begin greeting when Get(what), resume then resume.("RubyRussia")

    return =" message message.center(21, "-") end end Code def greeting name = ! Get("Your name?") "Hello #"name}!" end 45/130
  5. Handler def run_greeting begin greeting when Get(what), resume then resume.("RubyRussia")

    return =" message message.center(21, "-") end end Code def greeting name = ! Get("Your name?") "Hello #"name}!" end 46/130
  6. Handler def run_greeting begin greeting when Get(what), resume then resume.("RubyRussia")

    return =" message message.center(21, "-") end end Code def greeting name = ! Get("Your name?") "Hello #"name}!" end 47/130
  7. Handler def run_greeting begin greeting when Get(what), resume then resume.("RubyRussia")

    return =" message message.center(21, "-") end end Code def greeting name = ! Get("Your name?") "Hello #"name}!" end 48/130
  8. Handler def run_greeting begin greeting when Get(what), resume then resume.("RubyRussia")

    return =" message message.center(21, "-") end end Code def greeting name = ! Get("Your name?") "Hello #"name}!" end 49/130
  9. Handler def run_greeting begin greeting when Get(what), resume then resume.("RubyRussia")

    return =" message message.center(21, "-") end end run_greeting # =" "-$Hello RubyRussia!-$" Code def greeting name = ! Get("Your name?") "Hello #"name}!" end 50/130
  10. return can be inlined def run_greeting begin message = greeting

    message.center(21, "-") when Get(what), resume then resume.("RubyRussia") end end def greeting name = ! Get("Your name?") "Hello #"name}!" end 52/130
  11. How to jump back? def run_greeting begin message = greeting

    message.center(21, "-") when Get(what), resume then resume.("RubyRussia") end end def greeting name = ! Get("Your name?") "Hello #"name}!" end 53/130
  12. Using blocks def run_greeting message = greeting do |effect| case

    effect in Get(what) "RubyRussia" end end message.center(21, "-") end def greeting name = yield Get("Your name?") "Hello #"name}!" end 54/130
  13. Using blocks def run_greeting message = greeting do |effect| case

    effect in Get(what) "RubyRussia" end end message.center(21, "-") end def greeting name = yield Get("Your name?") "Hello #"name}!" end 55/130
  14. Using blocks def run_greeting message = greeting do |effect| case

    effect in Get(what) "RubyRussia" end end message.center(21, "-") end def greeting name = yield Get("Your name?") "Hello #"name}!" end 56/130
  15. Using global variables def run_greeting $handler = proc do |effect|

    case effect in Get(what) "RubyRussia" end end message = greeting message.center(21, "-") ensure $handler = nil end def greeting name = $handler.( Get("Your name?") ) "Hello #"name}!" end 57/130
  16. Using global variables This is how React works /" ReactFiberHooks.new.js

    /# ..% *' /" The work-in-progress fiber. let currentlyRenderingFiber: Fiber = (null: any); /" Hooks are stored as a linked list on the fiber's memoizedState field. The /" current hook list is the list that belongs to the current fiber. The /" work-in-progress hook list is a new list that will be added to the /" work-in-progress fiber. let currentHook: Hook | null = null; let workInProgressHook: Hook | null = null; /# ..% *' 58/130
  17. But you need to ensure def run_greeting $handler = proc

    do |effect| case effect in Get(what) "RubyRussia" end end message = greeting message.center(21, "-") ensure $handler = nil end 59/130
  18. Global variables work for single-threaded environments main -" run_greeting #-

    greeting Otherwise, it's not equivalent to the idea, seman5cally 63/130
  19. Parent fiber def run_greeting fiber = Fiber.new do greeting end

    ..# end Child fiber def greeting .." end 66/130
  20. Fiber handler def run_greeting fiber = Fiber.new { greeting }

    message = case fiber.resume in Get(what) fiber.resume("RubyRussia") end message.center(21, "-") end def greeting name = Fiber.yield( Get("Your name?") ) "Hello #"name}!" end 68/130
  21. Fiber handler def run_greeting fiber = Fiber.new { greeting }

    message = case fiber.resume in Get(what) fiber.resume("RubyRussia") end message.center(21, "-") end def greeting name = Fiber.yield( Get("Your name?") ) "Hello #"name}!" end 69/130
  22. Fiber handler def run_greeting fiber = Fiber.new { greeting }

    message = case fiber.resume in Get(what) fiber.resume("RubyRussia") end message.center(21, "-") end def greeting name = Fiber.yield( Get("Your name?") ) "Hello #"name}!" end 70/130
  23. Fiber handler def run_greeting fiber = Fiber.new { greeting }

    message = case fiber.resume in Get(what) fiber.resume("RubyRussia") end message.center(21, "-") end def greeting name = Fiber.yield( Get("Your name?") ) "Hello #"name}!" end 71/130
  24. Handling all effects def run_greeting fiber = Fiber.new { greeting

    } effect = fiber.resume loop do case effect in Get(what) effect = fiber.resume("RubyRussia") in message if !fiber.alive? break message.center(21, "-") end end end 74/130
  25. Handling all effects def run_greeting fiber = Fiber.new { greeting

    } effect = fiber.resume loop do case effect in Get(what) effect = fiber.resume("RubyRussia") in message if !fiber.alive? break message.center(21, "-") end end end 75/130
  26. Handler of Get loop do case effect in Get(what) effect

    = fiber.resume("RubyRussia") else if fiber.alive? effect = fiber.resume(Fiber.yield(effect)) else break effect end end end 81/130
  27. Handler of Get loop do case effect in Get(what) effect

    = fiber.resume("RubyRussia") else if fiber.alive? effect = fiber.resume(Fiber.yield(effect)) else break effect end end end 82/130
  28. Handler of Year loop do case effect in Year() effect

    = fiber.resume(2020) else if fiber.alive? effect = fiber.resume(Fiber.yield(effect)) else break effect end end end 83/130
  29. Handler of Year loop do case effect in Year() effect

    = fiber.resume(2020) else if fiber.alive? effect = fiber.resume(Fiber.yield(effect)) else break effect end end end 84/130
  30. Most effects can be handled by both fibers and global

    handlers —DI —Caching —Clock —Random —State —... 85/130
  31. Global Get handler def handle_get handlers = (Thread.current[:handlers] |"= [])

    last_handler = handlers.last handlers <% proc do |effect| case effect in Get(what) # return result else last_handler.(effect) end end run_code ensure handlers.pop end 86/130
  32. loop do case effect in Get(what) effect = fiber.resume("RubyRussia") else

    if fiber.alive? effect = fiber.resume(Fiber.yield(effect)) else break effect end end end 88/130
  33. Scheduling fibers case effect in Await(rs) if rs.ready? fiber.resume(rs.result) else

    queue <# [:wait, rs, fiber] # resume another fiber end end 89/130
  34. Mul$-shot con$nua$ons In full correspondence with the theory. Only with:

    - delimited con1nua1ons - call/cc Cons: - primi,ves don't exist in all languages - implementa,ons are not efficient - kinds of effects are really obscure: amb, backtracking 93/130
  35. Global Fibers (one-shot con2nua2ons) Delimited con2nua2ons (mul2-shot con2nua2ons) State DI

    Locking Cache Random Context Timeouts Parallel processing ... Async/await Generators Retry (simple) ExcepEons Retry (full) amb Backtracking 94/130
  36. Handlers are stored on the stack. Any stack manipula6on may

    break things badly. In most cases, workarounds are trivial (in Ruby). 98/130
  37. Some final thoughts —Generalizing things helps a bunch —You can

    add new effects as you go —You can always be sure they will be compa:ble with each other —Tes:ng is a breeze (even for complex stuff) —Fewer bugs in general? —It's fun 125/130