Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Going Functional with Algebraic Effects

Slide 3

Slide 3 text

! 3/118

Slide 4

Slide 4 text

Me Nikita Shilnikov — I write code — I write code in Ruby 4/118

Slide 5

Slide 5 text

Algebraic effects? 5/118

Slide 6

Slide 6 text

#hype 6/118

Slide 7

Slide 7 text

Algebraic effects are a new burrito 7/118

Slide 8

Slide 8 text

8/118

Slide 9

Slide 9 text

Already in production 9/118

Slide 10

Slide 10 text

React is wired with algebraic effects 10/118

Slide 11

Slide 11 text

Functional programming? 11/118

Slide 12

Slide 12 text

f = !" x { x + 5 } 12/118

Slide 13

Slide 13 text

Integer !" Integer f = !# x { x + 5 } 13/118

Slide 14

Slide 14 text

f = !" x { puts(x); x + 5 } 14/118

Slide 15

Slide 15 text

Launching rockets problem 15/118

Slide 16

Slide 16 text

doSomethingNice !" Integer !# IO () 16/118

Slide 17

Slide 17 text

Integer !" Integer, ? f = !# x { puts(x); x + 5 } 17/118

Slide 18

Slide 18 text

Integer !" Puts[Integer] : Integer f = !# x { puts(x); x + 5 } 18/118

Slide 19

Slide 19 text

Integer !" Puts[Integer] : Integer ^^^^^^^^^^^^^ f = !# x { puts(x); x + 5 } 19/118

Slide 20

Slide 20 text

Puts[Integer] is an effect 20/118

Slide 21

Slide 21 text

g = !" x { x + get(:y) } 21/118

Slide 22

Slide 22 text

Integer !" Get[Symbol !" Integer] : Integer g = !# x { x + get(:y) } 22/118

Slide 23

Slide 23 text

Integer !" Get[Symbol !" Integer] : Integer ^^^^^^^^^^^^^^^^^^^^^^ g = !# x { x + get(:y) } 23/118

Slide 24

Slide 24 text

Effects in type signatures reveal code's intentions 24/118

Slide 25

Slide 25 text

Effects are not side effects 25/118

Slide 26

Slide 26 text

f = !" x { puts(x); x + 5 } Side effects: Integer !" Integer Effects: Integer !" Puts[Integer] : Integer 26/118

Slide 27

Slide 27 text

To run effectful code you'll need a handler 27/118

Slide 28

Slide 28 text

f = !" x { puts(x); x + 5 } with_puts { f.(10) } 28/118

Slide 29

Slide 29 text

f = !" x { puts(x); x + 5 } with_puts { f.(10) } ^^^^^^^^^ 29/118

Slide 30

Slide 30 text

Every effect must have a handler 30/118

Slide 31

Slide 31 text

f = !" x { puts(x); x + 5 } f.(10) # !# Error! 31/118

Slide 32

Slide 32 text

The World 32/118

Slide 33

Slide 33 text

def greet print "Hello" end def main handle_print { greet } end 33/118

Slide 34

Slide 34 text

main handle_print greet print 34/118

Slide 35

Slide 35 text

World main # | handle_print # | greet # | print # ↓ 35/118

Slide 36

Slide 36 text

World main # | handle_print # | * greet # | ↑ print # ↓ | 36/118

Slide 37

Slide 37 text

World main # | handle_print # | * | greet # | ↑ | print # ↓ | ↓ 37/118

Slide 38

Slide 38 text

World main # | ↑ | # | | | greet # | | | print # ↓ | ↓ 38/118

Slide 39

Slide 39 text

World is the Handler 39/118

Slide 40

Slide 40 text

It's a side effect when it's handled by the World 40/118

Slide 41

Slide 41 text

Examples 41/118

Slide 42

Slide 42 text

Passing pseudo-global values around 42/118

Slide 43

Slide 43 text

class SetLocaleMiddleware def call(env) locale = detect_locale(env) with_locale(locale) { @app.(env) } end end 43/118

Slide 44

Slide 44 text

Testing features 44/118

Slide 45

Slide 45 text

Testing features class RenderView def call(values) if feature? render_with_feature(values) else render_without_feature(values) end end end 45/118

Slide 46

Slide 46 text

Testing features def call(env) feature_response, no_feature_response = with_feature do @app.(env) end if feature_response !" no_feature_response # !!# end end 46/118

Slide 47

Slide 47 text

Testing features def call(env) feature_response, no_feature_response = with_feature do @app.(env) end if feature_response !" no_feature_response # !!# end end 47/118

Slide 48

Slide 48 text

dry-effects 48/118

Slide 49

Slide 49 text

Controlling time 49/118

Slide 50

Slide 50 text

Accessing current time class CreatePost include Dry!"Effects.CurrentTime def call(values) publish_at = values[:publish_at] !# current_time # !!$ end end 50/118

Slide 51

Slide 51 text

Providing current time class WithCurrentTime include Dry!"Effects!"Handler.CurrentTime def call(env) with_current_time { @app.(env) } end end 51/118

Slide 52

Slide 52 text

Providing time class WithCurrentTime include Dry!"Effects!"Handler.CurrentTime def call(env) with_current_time { @app.(env) } end end 52/118

Slide 53

Slide 53 text

Testing include Dry!"Effects!"Handler.CurrentTime example do with_current_time { !!# } end 53/118

Slide 54

Slide 54 text

Testing RSpec.configure do |c| c.include Dry!"Effects!"Handler.CurrentTime now = Time.now c.around do |ex| with_current_time(proc { now }, &ex) end end 54/118

Slide 55

Slide 55 text

Testing example do next_day = Time.now + 86_400 with_current_time(proc { next_day }) { !!" } end 55/118

Slide 56

Slide 56 text

Dependency injection 56/118

Slide 57

Slide 57 text

Dependency injection class CreatePost include Dry!"Effects.Resolve(:post_repo) def call(values) if valid?(values) post_repo.create(values) else !!# end end end 57/118

Slide 58

Slide 58 text

Dependency injection class CreatePost include Dry!"Effects.Resolve(:post_repo) def call(values) if valid?(values) post_repo.create(values) else !!# end end end 58/118

Slide 59

Slide 59 text

Dependency injection class CreatePost include Dry!"Effects.Resolve(:post_repo) def call(values) if valid?(values) post_repo.create(values) else !!# end end end 59/118

Slide 60

Slide 60 text

Making a container AppContainer = { post_repo: PostRepo.new, !!" } 60/118

Slide 61

Slide 61 text

Providing dependencies class ProvideApplication include Dry!"Effects!"Handler.Resolve(AppContainer) def call(env) provide { @app.(env) } end end 61/118

Slide 62

Slide 62 text

Providing dependencies class ProvideApplication include Dry!"Effects!"Handler.Resolve(AppContainer) def call(env) provide { @app.(env) } end end 62/118

Slide 63

Slide 63 text

Testing include Dry!"Effects!"Handler.Resolve example do post_repo = double(:post_repo) provide(post_repo: post_repo) do # !!# end end 63/118

Slide 64

Slide 64 text

Tracing AppContainer = AppContainer.to_h do |key, value| [key, Wrapper.new(value)] end 64/118

Slide 65

Slide 65 text

Tracing AppContainer = AppContainer.to_h do |key, value| [key, Wrapper.new(value)] end 65/118

Slide 66

Slide 66 text

Batteries included Dry!"Effects.load_extensions(:system) class App < Dry!"Effects!"System!"Container Import = injector(!!#) end 66/118

Slide 67

Slide 67 text

Frozen application Dry!"Effects.load_extensions(:system) class App < Dry!"Effects!"System!"Container Import = injector(!!#) end # boot.rb App.finalize! 67/118

Slide 68

Slide 68 text

class CreateUser def initialize end def call end end 68/118

Slide 69

Slide 69 text

State 69/118

Slide 70

Slide 70 text

State class Add include Dry!"Effects.State(:result) def call(b) self.result += b nil end end 70/118

Slide 71

Slide 71 text

State class Mult include Dry!"Effects.State(:result) def call(b) self.result *= b nil end end 71/118

Slide 72

Slide 72 text

State class Calc include Dry!"Effects!"Handler.State(:result) def add Add.new end def mult Mult.new end end 72/118

Slide 73

Slide 73 text

State def call(x, y, z) with_result(x) do add.(y) mult.(z) " ! " end end 73/118

Slide 74

Slide 74 text

State calc = Calc.new calc.(5, 6, 7) 74/118

Slide 75

Slide 75 text

State calc = Calc.new calc.(5, 6, 7) # !" (5 + 6) * 7 !# 77 75/118

Slide 76

Slide 76 text

State calc = Calc.new calc.(5, 6, 7) # !" [77, " ! "] 76/118

Slide 77

Slide 77 text

All effects are composable* 77/118

Slide 78

Slide 78 text

Composition class Program include Dry!"Effects.Cmp(:feature) include Dry!"Effects.State(:counter) def call if feature? self.counter += 2 "bye" else self.counter += 1 "hi!" end end end 78/118

Slide 79

Slide 79 text

program = Program.new Dry!"Effects[:state, :counter].(10) do Dry!"Effects[:cmp, :feature].() do program.() end end # !# [13, ["hi!", "bye!"]] 79/118

Slide 80

Slide 80 text

program = Program.new Dry!"Effects[:cmp, :feature].() do Dry!"Effects[:state, :counter].(10) do program.() end end # !# [[11, "hi!"], [12, "bye!"]] 80/118

Slide 81

Slide 81 text

More examples 81/118

Slide 82

Slide 82 text

Timeout class MakeRequest include Dry!"Monads[:try] include Dry!"Effects.Timeout(:http) def call(url) Try() { HTTParty.get(url, timeout: timeout) } end end 82/118

Slide 83

Slide 83 text

Timeout class TimeoutMiddleware include Dry!"Effects!"Handler.Timeout(:http) def call(env) with_timeout(5.0) { @app.(env) } end end 83/118

Slide 84

Slide 84 text

Parallel class PullData include Dry!"Effects.Parallel def call(urls) join(urls.map { |url| par { make_request.(url) } }) end end 84/118

Slide 85

Slide 85 text

Parallel class PullData include Dry!"Effects.Parallel def call(urls) join(urls.map { |url| par { make_request.(url) } }) ^^^^ ^^^ end 85/118

Slide 86

Slide 86 text

Parallel class ParallelMiddleware include Dry!"Effects!"Handler.Parallel def call(env) with_parallel.() { @app.(env) } end end 86/118

Slide 87

Slide 87 text

dry-effects is a practical- oriented implementation 87/118

Slide 88

Slide 88 text

v0.1 Cache, Cmp, CurrentTime, Defer, Env, Implicit, Interrupt, Lock, Parallel, Random, Reader, Resolve, Retry, State, Timeout, Timestamp 88/118

Slide 89

Slide 89 text

Effects almost don't affect existing code 89/118

Slide 90

Slide 90 text

Just like monads, effects are language agnostic 90/118

Slide 91

Slide 91 text

91/118

Slide 92

Slide 92 text

Many shades of algebraic effects 92/118

Slide 93

Slide 93 text

Level 0 93/118

Slide 94

Slide 94 text

React 94/118

Slide 95

Slide 95 text

React.useState Dry!"Effects.CurrentTime 95/118

Slide 96

Slide 96 text

Level 1 96/118

Slide 97

Slide 97 text

dry-effects 97/118

Slide 98

Slide 98 text

Dry!"Effects.Retry Dry!"Effects.Parallel 98/118

Slide 99

Slide 99 text

Level 2 99/118

Slide 100

Slide 100 text

Fibers 100/118

Slide 101

Slide 101 text

Async/await 101/118

Slide 102

Slide 102 text

Async/await server.rb # scheduler !!" # ↑ user_repo.rb # find_user 102/118

Slide 103

Slide 103 text

Async/await server.rb # scheduler !!" # ↑ ↓ user_repo.rb # find_user 103/118

Slide 104

Slide 104 text

Level 3 104/118

Slide 105

Slide 105 text

Multi-shot continuations server.rb # scheduler !!" # ↑ ↓ ↓ ↓ user_repo.rb # find_user 105/118

Slide 106

Slide 106 text

Multi-shot continuations allow backtracking, parsers, etc. 106/118

Slide 107

Slide 107 text

callcc / Fiber#dup 107/118

Slide 108

Slide 108 text

Level 4 108/118

Slide 109

Slide 109 text

Typed effects 109/118

Slide 110

Slide 110 text

Algebraic effects are coming 110/118

Slide 111

Slide 111 text

It works Trust me! 111/118

Slide 112

Slide 112 text

Pros — New abilities — Easy to use — Already works (React!) — Easy to test — Traceable effects 112/118

Slide 113

Slide 113 text

Cons — Unfamiliar — Can be overused — Can be abused — Require glue code with threading 113/118

Slide 114

Slide 114 text

Next steps for dry-effects — Add async/await — Polishing APIs — More integrations with existing gems — More docs and examples — Multi-shot continuations? 114/118

Slide 115

Slide 115 text

It's not all 115/118

Slide 116

Slide 116 text

Learn more — github.com/topics/algebraic-effects — github.com/yallop/effects-bibliography 116/118

Slide 117

Slide 117 text

Thank you — twitter.com/NikitaShilnikov — dry-rb.org/gems/dry-effects — dry-rb.org/gems/dry-system — github.com/dry-rb/dry-effects — t.me/flash_gordon 117/118

Slide 118

Slide 118 text

Questions? 118/118