Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Going functional with algebraic effects
Search
Nikita Shilnikov
September 28, 2019
Technology
1
680
Going functional with algebraic effects
Introducing algebraic effects for Ruby with dry-effects
Nikita Shilnikov
September 28, 2019
Tweet
Share
More Decks by Nikita Shilnikov
See All by Nikita Shilnikov
How to algebraic effects
flashgordon
0
210
Typed Ruby
flashgordon
4
760
Other Decks in Technology
See All in Technology
会社紹介資料 / Sansan Company Profile
sansan33
PRO
6
380k
【実演版】カンファレンス登壇者・スタッフにこそ知ってほしいマイクの使い方 / 大吉祥寺.pm 2025
arthur1
1
890
RSCの時代にReactとフレームワークの境界を探る
uhyo
10
3.5k
「どこから読む?」コードとカルチャーに最速で馴染むための実践ガイド
zozotech
PRO
0
540
「Linux」という言葉が指すもの
sat
PRO
4
140
S3アクセス制御の設計ポイント
tommy0124
3
200
スクラムガイドに載っていないスクラムのはじめかた - チームでスクラムをはじめるときに知っておきたい勘所を集めてみました! - / How to start Scrum that is not written in the Scrum Guide 2nd
takaking22
1
110
テストを軸にした生き残り術
kworkdev
PRO
0
210
品質視点から考える組織デザイン/Organizational Design from Quality
mii3king
0
210
EncryptedSharedPreferences が deprecated になっちゃった!どうしよう! / Oh no! EncryptedSharedPreferences has been deprecated! What should I do?
yanzm
0
470
20250910_障害注入から効率的復旧へ_カオスエンジニアリング_生成AIで考えるAWS障害対応.pdf
sh_fk2
3
260
新アイテムをどう使っていくか?みんなであーだこーだ言ってみよう / 20250911-rpi-jam-tokyo
akkiesoft
0
320
Featured
See All Featured
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
656
61k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.5k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.7k
Balancing Empowerment & Direction
lara
3
620
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.1k
4 Signs Your Business is Dying
shpigford
184
22k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
44
2.5k
How to Ace a Technical Interview
jacobian
279
23k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
3k
GraphQLの誤解/rethinking-graphql
sonatard
72
11k
A designer walks into a library…
pauljervisheath
207
24k
Building Better People: How to give real-time feedback that sticks.
wjessup
368
19k
Transcript
None
Going Functional with Algebraic Effects
! 3/118
Me Nikita Shilnikov — I write code — I write
code in Ruby 4/118
Algebraic effects? 5/118
#hype 6/118
Algebraic effects are a new burrito 7/118
8/118
Already in production 9/118
React is wired with algebraic effects 10/118
Functional programming? 11/118
f = !" x { x + 5 } 12/118
Integer !" Integer f = !# x { x +
5 } 13/118
f = !" x { puts(x); x + 5 }
14/118
Launching rockets problem 15/118
doSomethingNice !" Integer !# IO () 16/118
Integer !" Integer, ? f = !# x { puts(x);
x + 5 } 17/118
Integer !" Puts[Integer] : Integer f = !# x {
puts(x); x + 5 } 18/118
Integer !" Puts[Integer] : Integer ^^^^^^^^^^^^^ f = !# x
{ puts(x); x + 5 } 19/118
Puts[Integer] is an effect 20/118
g = !" x { x + get(:y) } 21/118
Integer !" Get[Symbol !" Integer] : Integer g = !#
x { x + get(:y) } 22/118
Integer !" Get[Symbol !" Integer] : Integer ^^^^^^^^^^^^^^^^^^^^^^ g =
!# x { x + get(:y) } 23/118
Effects in type signatures reveal code's intentions 24/118
Effects are not side effects 25/118
f = !" x { puts(x); x + 5 }
Side effects: Integer !" Integer Effects: Integer !" Puts[Integer] : Integer 26/118
To run effectful code you'll need a handler 27/118
f = !" x { puts(x); x + 5 }
with_puts { f.(10) } 28/118
f = !" x { puts(x); x + 5 }
with_puts { f.(10) } ^^^^^^^^^ 29/118
Every effect must have a handler 30/118
f = !" x { puts(x); x + 5 }
f.(10) # !# Error! 31/118
The World 32/118
def greet print "Hello" end def main handle_print { greet
} end 33/118
main handle_print greet print 34/118
World main # | handle_print # | greet # |
print # ↓ 35/118
World main # | handle_print # | * greet #
| ↑ print # ↓ | 36/118
World main # | handle_print # | * | greet
# | ↑ | print # ↓ | ↓ 37/118
World main # | ↑ | # | | |
greet # | | | print # ↓ | ↓ 38/118
World is the Handler 39/118
It's a side effect when it's handled by the World
40/118
Examples 41/118
Passing pseudo-global values around 42/118
class SetLocaleMiddleware def call(env) locale = detect_locale(env) with_locale(locale) { @app.(env)
} end end 43/118
Testing features 44/118
Testing features class RenderView def call(values) if feature? render_with_feature(values) else
render_without_feature(values) end end end 45/118
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
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
dry-effects 48/118
Controlling time 49/118
Accessing current time class CreatePost include Dry!"Effects.CurrentTime def call(values) publish_at
= values[:publish_at] !# current_time # !!$ end end 50/118
Providing current time class WithCurrentTime include Dry!"Effects!"Handler.CurrentTime def call(env) with_current_time
{ @app.(env) } end end 51/118
Providing time class WithCurrentTime include Dry!"Effects!"Handler.CurrentTime def call(env) with_current_time {
@app.(env) } end end 52/118
Testing include Dry!"Effects!"Handler.CurrentTime example do with_current_time { !!# } end
53/118
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
Testing example do next_day = Time.now + 86_400 with_current_time(proc {
next_day }) { !!" } end 55/118
Dependency injection 56/118
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
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
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
Making a container AppContainer = { post_repo: PostRepo.new, !!" }
60/118
Providing dependencies class ProvideApplication include Dry!"Effects!"Handler.Resolve(AppContainer) def call(env) provide {
@app.(env) } end end 61/118
Providing dependencies class ProvideApplication include Dry!"Effects!"Handler.Resolve(AppContainer) def call(env) provide {
@app.(env) } end end 62/118
Testing include Dry!"Effects!"Handler.Resolve example do post_repo = double(:post_repo) provide(post_repo: post_repo)
do # !!# end end 63/118
Tracing AppContainer = AppContainer.to_h do |key, value| [key, Wrapper.new(value)] end
64/118
Tracing AppContainer = AppContainer.to_h do |key, value| [key, Wrapper.new(value)] end
65/118
Batteries included Dry!"Effects.load_extensions(:system) class App < Dry!"Effects!"System!"Container Import = injector(!!#)
end 66/118
Frozen application Dry!"Effects.load_extensions(:system) class App < Dry!"Effects!"System!"Container Import = injector(!!#)
end # boot.rb App.finalize! 67/118
class CreateUser def initialize end def call end end 68/118
State 69/118
State class Add include Dry!"Effects.State(:result) def call(b) self.result += b
nil end end 70/118
State class Mult include Dry!"Effects.State(:result) def call(b) self.result *= b
nil end end 71/118
State class Calc include Dry!"Effects!"Handler.State(:result) def add Add.new end def
mult Mult.new end end 72/118
State def call(x, y, z) with_result(x) do add.(y) mult.(z) "
! " end end 73/118
State calc = Calc.new calc.(5, 6, 7) 74/118
State calc = Calc.new calc.(5, 6, 7) # !" (5
+ 6) * 7 !# 77 75/118
State calc = Calc.new calc.(5, 6, 7) # !" [77,
" ! "] 76/118
All effects are composable* 77/118
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
program = Program.new Dry!"Effects[:state, :counter].(10) do Dry!"Effects[:cmp, :feature].() do program.()
end end # !# [13, ["hi!", "bye!"]] 79/118
program = Program.new Dry!"Effects[:cmp, :feature].() do Dry!"Effects[:state, :counter].(10) do program.()
end end # !# [[11, "hi!"], [12, "bye!"]] 80/118
More examples 81/118
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
Timeout class TimeoutMiddleware include Dry!"Effects!"Handler.Timeout(:http) def call(env) with_timeout(5.0) { @app.(env)
} end end 83/118
Parallel class PullData include Dry!"Effects.Parallel def call(urls) join(urls.map { |url|
par { make_request.(url) } }) end end 84/118
Parallel class PullData include Dry!"Effects.Parallel def call(urls) join(urls.map { |url|
par { make_request.(url) } }) ^^^^ ^^^ end 85/118
Parallel class ParallelMiddleware include Dry!"Effects!"Handler.Parallel def call(env) with_parallel.() { @app.(env)
} end end 86/118
dry-effects is a practical- oriented implementation 87/118
v0.1 Cache, Cmp, CurrentTime, Defer, Env, Implicit, Interrupt, Lock, Parallel,
Random, Reader, Resolve, Retry, State, Timeout, Timestamp 88/118
Effects almost don't affect existing code 89/118
Just like monads, effects are language agnostic 90/118
91/118
Many shades of algebraic effects 92/118
Level 0 93/118
React 94/118
React.useState Dry!"Effects.CurrentTime 95/118
Level 1 96/118
dry-effects 97/118
Dry!"Effects.Retry Dry!"Effects.Parallel 98/118
Level 2 99/118
Fibers 100/118
Async/await 101/118
Async/await server.rb # scheduler !!" # ↑ user_repo.rb # find_user
102/118
Async/await server.rb # scheduler !!" # ↑ ↓ user_repo.rb #
find_user 103/118
Level 3 104/118
Multi-shot continuations server.rb # scheduler !!" # ↑ ↓ ↓
↓ user_repo.rb # find_user 105/118
Multi-shot continuations allow backtracking, parsers, etc. 106/118
callcc / Fiber#dup 107/118
Level 4 108/118
Typed effects 109/118
Algebraic effects are coming 110/118
It works Trust me! 111/118
Pros — New abilities — Easy to use — Already
works (React!) — Easy to test — Traceable effects 112/118
Cons — Unfamiliar — Can be overused — Can be
abused — Require glue code with threading 113/118
Next steps for dry-effects — Add async/await — Polishing APIs
— More integrations with existing gems — More docs and examples — Multi-shot continuations? 114/118
It's not all 115/118
Learn more — github.com/topics/algebraic-effects — github.com/yallop/effects-bibliography 116/118
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
Questions? 118/118