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
650
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
200
Typed Ruby
flashgordon
4
750
Other Decks in Technology
See All in Technology
AIオンボーディングとAIプロセスマイニング
nrryuya
5
970
The Ultimate Showdown of Database Migration Tools
asm0dey
0
130
ゼロコードで実現! - OpenTelemetryとOCI APM Agentによる簡単アプリケーション監視 - / Zero-Code Observability with OpenTelemetry and OCI APM
oracle4engineer
PRO
1
170
Scale Security Programs with Scorecarding
ramimac
0
310
名刺メーカーDevグループ 紹介資料
sansan33
PRO
0
730
変化に強いテーブル設計の勘所 / Table design that is resistant to changes
soudai
48
13k
グループ ポリシー再確認 ③
murachiakira
0
140
トイルを撲滅!インフラ領域での生成AI活用のススメ
shuya
0
340
君だけのオリジナル async / await を作ろう / TSKaigi 2025
susisu
17
12k
AIのための オンボーディングドキュメントを整備する - hirotea
hirotea
8
1.9k
プラットフォームとしての Datadog / Datadog as Platforms
aoto
PRO
1
270
それでもぼくらは貢献をつづけるのだ(たぶん) @FOSS4GLT会#002
furukawayasuto
1
230
Featured
See All Featured
The Cost Of JavaScript in 2023
addyosmani
49
7.9k
Thoughts on Productivity
jonyablonski
69
4.7k
Designing for Performance
lara
608
69k
Art, The Web, and Tiny UX
lynnandtonic
298
21k
Large-scale JavaScript Application Architecture
addyosmani
512
110k
Bootstrapping a Software Product
garrettdimon
PRO
307
110k
StorybookのUI Testing Handbookを読んだ
zakiyama
30
5.7k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
32
5.8k
Into the Great Unknown - MozCon
thekraken
38
1.8k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
30
2.4k
GraphQLとの向き合い方2022年版
quramy
46
14k
Rebuilding a faster, lazier Slack
samanthasiow
81
9k
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