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