Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

How to algebraic effects 2/130

Slide 3

Slide 3 text

Me Nikita Shilnikov I write code in Ruby 3/130

Slide 4

Slide 4 text

What are algebraic effects? 4/130

Slide 5

Slide 5 text

5/130

Slide 6

Slide 6 text

Category theory? 6/130

Slide 7

Slide 7 text

Algebraic effects12 2 2009, G.Plotkin, M.Pretnar 1 2003, G.Plotkin, J.Power 7/130

Slide 8

Slide 8 text

2003 8/130

Slide 9

Slide 9 text

2009 9/130

Slide 10

Slide 10 text

1989–2020 10/130

Slide 11

Slide 11 text

Effects for the masses 11/130

Slide 12

Slide 12 text

2018 — React Hooks 12/130

Slide 13

Slide 13 text

2018 — React Hooks Brought to us by 1 person more or less* * Sebas(an Markbåge 13/130

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Be#er than DI 15/130

Slide 16

Slide 16 text

Be#er than async/await 16/130

Slide 17

Slide 17 text

Be#er than dozens of special-purposed libraries 17/130

Slide 18

Slide 18 text

Example: JavaScript —Async func*ons —Generator func*ons —Async generator func*ons (new feature!) —What's next? ☠ 18/130

Slide 19

Slide 19 text

Why? —It's proven to be working —Unified solu7on —Good for reasoning 19/130

Slide 20

Slide 20 text

Previously (RubyRussia 2019) How to use algebraic effects 20/130

Slide 21

Slide 21 text

Today How to implement algebraic effects 21/130

Slide 22

Slide 22 text

You can use this talk as a troubleshoo1ng guide 22/130

Slide 23

Slide 23 text

Spoiler alert ⚠ lots of code 23/130

Slide 24

Slide 24 text

Using algebraic effects is like riding a bike 24/130

Slide 25

Slide 25 text

But there are two parts Effects Handlers 25/130

Slide 26

Slide 26 text

Part 1 of 2 Effects (opera-ons) 26/130

Slide 27

Slide 27 text

Opera&on is an effect constructor 27/130

Slide 28

Slide 28 text

Opera&on is an effect constructor def greeting "Hello #"Get("Your name?")}!" end 28/130

Slide 29

Slide 29 text

Opera&on is an effect constructor def greeting "Hello #"Get("Your name?")}!" end It's just an addi,on to your code. 29/130

Slide 30

Slide 30 text

Opera&on is an effect constructor def greeting "Hello #" ! Get("Your name?")}!" end 30/130

Slide 31

Slide 31 text

Part 2 of 2 Effect handlers 31/130

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

How it works 40/130

Slide 41

Slide 41 text

41/130

Slide 42

Slide 42 text

42/130

Slide 43

Slide 43 text

43/130

Slide 44

Slide 44 text

44/130

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Let's make it real 51/130

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Global variables work for single-threaded environments main -" run_greeting -" greeting 60/130

Slide 61

Slide 61 text

Global variables work for single-threaded environments main -" run_greeting -" greeting 61/130

Slide 62

Slide 62 text

Global variables work for single-threaded environments main -" run_greeting #- greeting 62/130

Slide 63

Slide 63 text

Global variables work for single-threaded environments main -" run_greeting #- greeting Otherwise, it's not equivalent to the idea, seman5cally 63/130

Slide 64

Slide 64 text

Leveling up: fibers 64/130

Slide 65

Slide 65 text

Fiber: goto + extra features 65/130

Slide 66

Slide 66 text

Parent fiber def run_greeting fiber = Fiber.new do greeting end ..# end Child fiber def greeting .." end 66/130

Slide 67

Slide 67 text

67/130

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

We need to loop 72/130

Slide 73

Slide 73 text

Looping def greeting fname = Fiber.yield("first name") lname = Fiber.yield("last name") "Hello ##fname} ##lname}!" end 73/130

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Composability def greeting name = Fiber.yield(Get(:name)) year = Fiber.yield(Year()) "Hello #"name} #"year}!" end 76/130

Slide 77

Slide 77 text

Dispatching effects 77/130

Slide 78

Slide 78 text

Dispatching effects 78/130

Slide 79

Slide 79 text

Dispatching effects 79/130

Slide 80

Slide 80 text

Dispatching effects 80/130

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Most effects can be handled by both fibers and global handlers —DI —Caching —Clock —Random —State —... 85/130

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Fibers vs Global handlers 87/130

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

How it works def greeting name = Fiber.yield( Get("Your name?") ) "Hello #"name}!" end 90/130

Slide 91

Slide 91 text

Other ways: - delimited con4nua4ons - generators - call/cc, require 'continuation' ( ) 91/130

Slide 92

Slide 92 text

More effects with mul0-shot con0nua0ons 92/130

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

Algebraic effects vs. real world 95/130

Slide 96

Slide 96 text

Handlers are stored on the stack. 96/130

Slide 97

Slide 97 text

Handlers are stored on the stack. Any stack manipula6on may break things badly. 97/130

Slide 98

Slide 98 text

Handlers are stored on the stack. Any stack manipula6on may break things badly. In most cases, workarounds are trivial (in Ruby). 98/130

Slide 99

Slide 99 text

Problem 1: missing handlers 99/130

Slide 100

Slide 100 text

Missing handlers —You forgot to add a handler —Stack was dropped 100/130

Slide 101

Slide 101 text

handle_year do Thread.new do Fiber.yield(Year()) end end 101/130

Slide 102

Slide 102 text

handle_year do Thread.new do # stack is reset Fiber.yield(Year()) end end 102/130

Slide 103

Slide 103 text

handle_year do Thread.new do Fiber.yield(Year()) end end 103/130

Slide 104

Slide 104 text

Solu%on: copy handler stack 104/130

Slide 105

Slide 105 text

Transferring handlers 105/130

Slide 106

Slide 106 text

Transferring handlers This is what React does when you call setState 106/130

Slide 107

Slide 107 text

Copying can be expensive but you may save some alloca4ons with immutable handlers 107/130

Slide 108

Slide 108 text

Missing handlers —You forgot to add a handler —Stack was dropped —Leaked effects 108/130

Slide 109

Slide 109 text

Leaked effects —Tricky indeed —Are not common for Ruby 109/130

Slide 110

Slide 110 text

def lazy_greeting -" { "Hello #$Get("name?")}" } end 110/130

Slide 111

Slide 111 text

def run_greeting greeting = handle_get { lazy_greeting } greeting.() end 111/130

Slide 112

Slide 112 text

def run_greeting greeting = handle_get { lazy_greeting } greeting.() ! end 112/130

Slide 113

Slide 113 text

Leaking effects may be a limita1on 113/130

Slide 114

Slide 114 text

Problem 2: interop 114/130

Slide 115

Slide 115 text

Interop —Thread-local variables are actually fiber-local in Ruby —Some libraries may be very opinionated (avoid) 115/130

Slide 116

Slide 116 text

Thread-local storage Thread.current[:value] = :foo Fiber.new { Thread.current[:value] }.resume # =" nil 116/130

Slide 117

Slide 117 text

Thread-local storage Solu%on: monkey patches :( 117/130

Slide 118

Slide 118 text

Problem 3: new kinds of bugs Frozen (me, caching, locking, etc 118/130

Slide 119

Slide 119 text

Problem 4: debugging 119/130

Slide 120

Slide 120 text

Debugging Call stacks may become messy (but it depends) 120/130

Slide 121

Slide 121 text

Debugging Some tools may be required (e.g. handlers stack inspec9on) 121/130

Slide 122

Slide 122 text

Problem 5: code requires handlers 122/130

Slide 123

Slide 123 text

Code requires handlers greeting ! 123/130

Slide 124

Slide 124 text

Problem 6: performance limita2ons? 124/130

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

Fibers/con+nua+ons become more available 126/130

Slide 127

Slide 127 text

Effect libraries will follow 127/130

Slide 128

Slide 128 text

Other languages? 128/130

Slide 129

Slide 129 text

At least in Ruby we have dry-effects ;) 129/130

Slide 130

Slide 130 text

Thank you —github.com/yallop/effects-bibliography —twi7er.com/NikitaShilnikov —dry-rb.org/gems/dry-effects —t.me/flash_gordon 130/130