slidenumbers: true slidecount: true code: auto(4), text-scale(1), Fira Code list: alignment(left), bullet-character(—)
[.slidenumbers: false]
[.slidenumbers: false]
[.list: alignment(center), bullet-character(—)]
- I write code
- I write code in Ruby
 ` `` `` ` 
[.code: text-scale(1.6)]
f = -> x { x + 5 }
[.code: text-scale(1.6)]
Integer => Integer
f = -> x { x + 5 }
[.code: text-scale(1.6)]
f = -> x { puts(x); x + 5 }
[.code: text-scale(1.6)]
doSomethingNice :: Integer -> IO ()
[.code: text-scale(1.6)]
Integer => Integer, ?
f = -> x { puts(x); x + 5 }
[.code: text-scale(1.6)]
Integer => Puts[Integer] : Integer
f = -> x { puts(x); x + 5 }
[.code: text-scale(1.6)] [.code-highlight: 1-2]
Integer => Puts[Integer] : Integer
^^^^^^^^^^^^^
f = -> x { puts(x); x + 5 }
[.code: text-scale(1.6)]
g = -> x { x + get(:y) }
[.code: text-scale(1.6)]
Integer => Get[Symbol => Integer] : Integer
g = -> x { x + get(:y) }
[.code: text-scale(1.6)] [.code-highlight: 1-2]
Integer => Get[Symbol => Integer] : Integer
^^^^^^^^^^^^^^^^^^^^^^
g = -> x { x + get(:y) }
[.code: text-scale(1.3)] [.text: alignment(left)]
f = -> x { puts(x); x + 5 }
[.code: text-scale(1.5)]
f = -> x { puts(x); x + 5 }
with_puts { f.(10) }
[.code: text-scale(1.5)] [.code-highlight: 2-3]
f = -> x { puts(x); x + 5 }
with_puts { f.(10) }
^^^^^^^^^
[.code: text-scale(1.5)]
f = -> x { puts(x); x + 5 }
f.(10) # => Error!
[.code: text-scale(1.6)]
def greet
print "Hello"
end
def main
handle_print { greet }
end
[.code: text-scale(1.8)]
main
handle_print
greet
print
[.code: text-scale(1.8)]
World
main # |
handle_print # |
greet # |
print # ↓
[.code: text-scale(1.8)]
World
main # |
handle_print # | *
greet # | ↑
print # ↓ |
[.code: text-scale(1.8)]
World
main # |
handle_print # | * |
greet # | ↑ |
print # ↓ | ↓
[.code: text-scale(1.8)]
World
main # | ↑ |
# | | |
greet # | | |
print # ↓ | ↓
[.code: text-scale(1.6)]
class SetLocaleMiddleware
def call(env)
locale = detect_locale(env)
with_locale(locale) { @app.(env) }
end
end
[.code: text-scale(1.5)]
class RenderView
def call(values)
if feature?
render_with_feature(values)
else
render_without_feature(values)
end
end
end
[.code: text-scale(1.5)] [.code-highlight: 1-4,9]
def call(env)
feature_response, no_feature_response = with_feature do
@app.(env)
end
if feature_response != no_feature_response
# ...
end
end
def call(env)
feature_response, no_feature_response = with_feature do
@app.(env)
end
if feature_response != no_feature_response
# ...
end
end
class CreatePost
include Dry::Effects.CurrentTime
def call(values)
publish_at = values[:publish_at] || current_time
# ...
end
end
[.code-highlight: 1,4,6-7]
class WithCurrentTime
include Dry::Effects::Handler.CurrentTime
def call(env)
with_current_time { @app.(env) }
end
end
[.code-highlight: 2,5]
class WithCurrentTime
include Dry::Effects::Handler.CurrentTime
def call(env)
with_current_time { @app.(env) }
end
end
include Dry::Effects::Handler.CurrentTime
example do
with_current_time { ... }
end
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
example do
next_day = Time.now + 86_400
with_current_time(proc { next_day }) { ... }
end
class CreatePost
include Dry::Effects.Resolve(:post_repo)
def call(values)
if valid?(values)
post_repo.create(values)
else
...
end
end
end
[.code-highlight: 2]
class CreatePost
include Dry::Effects.Resolve(:post_repo)
def call(values)
if valid?(values)
post_repo.create(values)
else
...
end
end
end
[.code-highlight: 2,6]
class CreatePost
include Dry::Effects.Resolve(:post_repo)
def call(values)
if valid?(values)
post_repo.create(values)
else
...
end
end
end
AppContainer = {
post_repo: PostRepo.new,
...
}
class ProvideApplication
include Dry::Effects::Handler.Resolve(AppContainer)
def call(env)
provide { @app.(env) }
end
end
[.code-highlight: 2,5]
class ProvideApplication
include Dry::Effects::Handler.Resolve(AppContainer)
def call(env)
provide { @app.(env) }
end
end
include Dry::Effects::Handler.Resolve
example do
post_repo = double(:post_repo)
provide(post_repo: post_repo) do
# ...
end
end
[.code: text-scale(1.5)]
AppContainer = AppContainer.to_h do |key, value|
[key, Wrapper.new(value)]
end
[.code: text-scale(1.5)]
AppContainer = AppContainer.to_h do |key, value|
[key, Wrapper.new(value)]
end
Dry::Effects.load_extensions(:system)
class App < Dry::Effects::System::Container
Import = injector(...)
end
[.code-highlight: 7-9]
Dry::Effects.load_extensions(:system)
class App < Dry::Effects::System::Container
Import = injector(...)
end
# boot.rb
App.finalize!
[.code: text-scale(1.5)]
class CreateUser
def initialize
end
def call
end
end
class Add
include Dry::Effects.State(:result)
def call(b)
self.result += b
nil
end
end
class Mult
include Dry::Effects.State(:result)
def call(b)
self.result *= b
nil
end
end
class Calc
include Dry::Effects::Handler.State(:result)
def add
Add.new
end
def mult
Mult.new
end
end
def call(x, y, z)
with_result(x) do
add.(y)
mult.(z)
"🍏"
end
end
calc = Calc.new
calc.(5, 6, 7)
calc = Calc.new
calc.(5, 6, 7) # => (5 + 6) * 7 == 77
calc = Calc.new
calc.(5, 6, 7)
# => [77, "🍏"]
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
program = Program.new
Dry::Effects[:state, :counter].(10) do
Dry::Effects[:cmp, :feature].() do
program.()
end
end
# => [13, ["hi!", "bye!"]]
program = Program.new
Dry::Effects[:cmp, :feature].() do
Dry::Effects[:state, :counter].(10) do
program.()
end
end
# => [[11, "hi!"], [12, "bye!"]]
class MakeRequest
include Dry::Monads[:try]
include Dry::Effects.Timeout(:http)
def call(url)
Try() { HTTParty.get(url, timeout: timeout) }
end
end
class TimeoutMiddleware
include Dry::Effects::Handler.Timeout(:http)
def call(env)
with_timeout(5.0) { @app.(env) }
end
end
class PullData
include Dry::Effects.Parallel
def call(urls)
join(urls.map { |url| par { make_request.(url) } })
end
end
[.code-highlight: 5-6]
class PullData
include Dry::Effects.Parallel
def call(urls)
join(urls.map { |url| par { make_request.(url) } })
^^^^ ^^^
end
class ParallelMiddleware
include Dry::Effects::Handler.Parallel
def call(env)
with_parallel.() { @app.(env) }
end
end
Cache, Cmp, CurrentTime, Defer, Env, Implicit, Interrupt, Lock, Parallel, Random, Reader, Resolve, Retry, State, Timeout, Timestamp
server.rb # scheduler
... # ↑
user_repo.rb # find_user
server.rb # scheduler
... # ↑ ↓
user_repo.rb # find_user
server.rb # scheduler
... # ↑ ↓ ↓ ↓
user_repo.rb # find_user
- New abilities
- Easy to use
- Already works (React!)
- Easy to test
- Traceable effects
- Unfamiliar
- Can be overused
- Can be abused
- Require glue code with threading
- Add async/await
- Polishing APIs
- More integrations with existing gems
- More docs and examples
- Multi-shot continuations?
- 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