Interrupt
Interrupt is an effect with the semantics of raise
/rescue
or throw
/catch
. It's added for consistency and compatibility with other effects. Underneath, it uses raise
+ rescue
so that application code can detect the bubbling.
Basic usage
If you know what exceptions are, this should look familiar:
require 'dry/effects'
class RunDivision
include Dry::Effects::Handler.Interrupt(:division_by_zero, as: :catch_zero_division)
def call
success, answer = catch_zero_division do
yield
end
if success
answer
else
:error
end
end
end
class Divide
include Dry::Effects.Interrupt(:division_by_zero)
def call(dividend, divisor)
if divisor.zero?
division_by_zero
else
dividend / divisor
end
end
end
run = RunDivision.new
divide = Divide.new
app = -> a, b { run.() { divide.(a, b) } }
app.(10, 2) # => 5
app.(1, 0) # => :error
The handler returns a flag indicating whether there was an interruption. false
means the block was run without interruption, true
stands for the code was interrupted at some point.
Payload
Interruption can have a payload:
class Callee
include Dry::Effects.Interrupt(:halt)
def call
halt :foo
end
end
class Caller
include Dry::Effects::Handler.Interrupt(:halt, as: :catch_halt)
def call
_, result = catch_halt do
yield
:bar
end
result
end
end
caller = Caller.new
callee = Callee.new
caller.() { callee.() } # => :foo
caller.() { } # => :bar
Composition
Every Interrupt effect has to have an identifier so that they don't overlap. It's an equivalent of exception types. You can nest handlers with different identifiers; they will work just as you would expect:
class Catcher
include Dry::Effects::Handler(:div_error, as: :catch_div)
include Dry::Effects::Handler(:sqrt_error, as: :catch_sqrt)
def call
_, div_result = catch_div do
_, sqrt_result = catch_sqrt do
yield
end
sqrt_result
end
div_result
end
end