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

How to algebraic effects

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

How to algebraic effects

Avatar for Nikita Shilnikov

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