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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Nikita Shilnikov
September 28, 2019
Technology
850
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Going functional with algebraic effects
Introducing algebraic effects for Ruby with dry-effects
Nikita Shilnikov
September 28, 2019
More Decks by Nikita Shilnikov
See All by Nikita Shilnikov
How to algebraic effects
flashgordon
0
240
Typed Ruby
flashgordon
4
830
Other Decks in Technology
See All in Technology
あなたの知らないPDFのアクセシビリティ
lycorptech_jp
PRO
0
200
LayerX コーポレートエンジニアリング室におけるサプライチェーンセキュリティへの取り組み / Supply Chain Security at LayerX Corporate Engineering
yuyatakeyama
2
600
入門!AWS Blocks
ysuzuki
1
140
Claude Codeとのおしゃべりでセマンティックモデルの定義からダッシュボード作成まで完成させる
nic_sugiyama
0
120
小さくはじめるSLI/SLO ~育てながら組織に定着させる実践知~ / Starting Small with SLI/SLOs: Building Adoption Through Continuous Growth
nari_ex
7
2k
SONiCのLinuxベースを活かしたZabbix監視
sonic
0
180
Disciplined Vibes: Scaling AI-Assisted Engineering
sheharyar
0
150
アンオフィシャルな、オフィシャルからのお願い
wyamazak_devrel
0
120
新しいUbuntu/GNOMEが使いたいからXからWaylandへ移行頑張ってるの巻 2026-06-20
nobutomurata
0
130
日本 Fintech 未来予測レポート 2027〜2028年(手動編集版)
8maki
0
2.4k
アジャイルな経理と Claude Code と経営の未来
kawaguti
PRO
3
140
Oracle AI Database@Azure:サービス概要のご紹介
oracle4engineer
PRO
6
2k
Featured
See All Featured
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
190
Why You Should Never Use an ORM
jnunemaker
PRO
61
9.9k
Agile that works and the tools we love
rasmusluckow
331
21k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.1k
The World Runs on Bad Software
bkeepers
PRO
72
12k
A Soul's Torment
seathinner
6
2.9k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
360
The Curse of the Amulet
leimatthew05
1
13k
Accessibility Awareness
sabderemane
1
140
Optimizing for Happiness
mojombo
378
71k
Thoughts on Productivity
jonyablonski
76
5.2k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
160
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