Nikita Shilnikov
September 28, 2019
420

# Going functional with algebraic effects

Introducing algebraic effects for Ruby with dry-effects

## Nikita Shilnikov

September 28, 2019

## Transcript

1. None

4. ### Me Nikita Shilnikov — I write code — I write

code in Ruby 4/118

5 } 13/118

14/118

17. ### Integer !" Integer, ? f = !# x { puts(x);

x + 5 } 17/118
18. ### Integer !" Puts[Integer] : Integer f = !# x {

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

{ puts(x); x + 5 } 19/118

22. ### Integer !" Get[Symbol !" Integer] : Integer g = !#

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

!# x { x + get(:y) } 23/118

26. ### f = !" x { puts(x); x + 5 }

Side effects: Integer !" Integer Effects: Integer !" Puts[Integer] : Integer 26/118

28. ### f = !" x { puts(x); x + 5 }

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

with_puts { f.(10) } ^^^^^^^^^ 29/118

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

f.(10) # !# Error! 31/118

} end 33/118

35. ### World main # | handle_print # | greet # |

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

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

# | ↑ | print # ↓ | ↓ 37/118
38. ### World main # | ↑ | # | | |

greet # | | | print # ↓ | ↓ 38/118

40/118

43. ### class SetLocaleMiddleware def call(env) locale = detect_locale(env) with_locale(locale) { @app.(env)

} end end 43/118

45. ### Testing features class RenderView def call(values) if feature? render_with_feature(values) else

render_without_feature(values) end end end 45/118
46. ### 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
47. ### 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

50. ### Accessing current time class CreatePost include Dry!"Effects.CurrentTime def call(values) publish_at

= values[:publish_at] !# current_time # !!\$ end end 50/118
51. ### Providing current time class WithCurrentTime include Dry!"Effects!"Handler.CurrentTime def call(env) with_current_time

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

@app.(env) } end end 52/118

53/118
54. ### 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
55. ### Testing example do next_day = Time.now + 86_400 with_current_time(proc {

next_day }) { !!" } end 55/118

57. ### 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
58. ### 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
59. ### 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

60/118
61. ### Providing dependencies class ProvideApplication include Dry!"Effects!"Handler.Resolve(AppContainer) def call(env) provide {

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

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

do # !!# end end 63/118

64/118

65/118

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

end # boot.rb App.finalize! 67/118

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

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

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

mult Mult.new end end 72/118
73. ### State def call(x, y, z) with_result(x) do add.(y) mult.(z) "

! " end end 73/118

75. ### State calc = Calc.new calc.(5, 6, 7) # !" (5

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

" ! "] 76/118

78. ### 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
79. ### program = Program.new Dry!"Effects[:state, :counter].(10) do Dry!"Effects[:cmp, :feature].() do program.()

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

end end # !# [[11, "hi!"], [12, "bye!"]] 80/118

82. ### 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
83. ### Timeout class TimeoutMiddleware include Dry!"Effects!"Handler.Timeout(:http) def call(env) with_timeout(5.0) { @app.(env)

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

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

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

} end end 86/118

88. ### v0.1 Cache, Cmp, CurrentTime, Defer, Env, Implicit, Interrupt, Lock, Parallel,

Random, Reader, Resolve, Retry, State, Timeout, Timestamp 88/118

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

find_user 103/118

105. ### Multi-shot continuations server.rb # scheduler !!" # ↑ ↓ ↓

↓ user_repo.rb # find_user 105/118

112. ### Pros — New abilities — Easy to use — Already

works (React!) — Easy to test — Traceable effects 112/118
113. ### Cons — Unfamiliar — Can be overused — Can be

abused — Require glue code with threading 113/118
114. ### Next steps for dry-effects — Add async/await — Polishing APIs

— More integrations with existing gems — More docs and examples — Multi-shot continuations? 114/118