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