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
670
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
Eval-Centric AI: Agent 開発におけるベストプラクティスの探求
asei
0
110
Amazon Q と『音楽』-ゲーム音楽もAmazonQで作成してみた感想-
senseofunity129
0
130
Lambda management with ecspresso and Terraform
ijin
2
160
家族の思い出を形にする 〜 1秒動画の生成を支えるインフラアーキテクチャ
ojima_h
3
890
AIのグローバルトレンド 2025 / ai global trend 2025
kyonmm
PRO
1
130
Bet "Bet AI" - Accelerating Our AI Journey #BetAIDay
layerx
PRO
4
1.7k
MCP認可の現在地と自律型エージェント対応に向けた課題 / MCP Authorization Today and Challenges to Support Autonomous Agents
yokawasa
5
2.2k
ホリスティックテスティングの右側も大切にする 〜2つの[はか]る〜 / Holistic Testing: Right Side Matters
nihonbuson
PRO
0
660
【CEDEC2025】大規模言語モデルを活用したゲーム内会話パートのスクリプト作成支援への取り組み
cygames
PRO
2
820
LLMでAI-OCR、実際どうなの? / llm_ai_ocr_layerx_bet_ai_day_lt
sbrf248
0
450
Findy Freelance 利用シーン別AI活用例
ness
0
410
マルチプロダクト×マルチテナントを支えるモジュラモノリスを中心としたアソビューのアーキテクチャ
disc99
1
410
Featured
See All Featured
The Art of Programming - Codeland 2020
erikaheidi
54
13k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
110
19k
The World Runs on Bad Software
bkeepers
PRO
70
11k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
50k
Why You Should Never Use an ORM
jnunemaker
PRO
58
9.5k
Statistics for Hackers
jakevdp
799
220k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
183
54k
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.6k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
KATA
mclloyd
32
14k
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