dry-monads: flaws or intended behaviour?

This is an intentional design choice to support transaction rollbacks, and altering the behavior would be a breaking change. Exceptions as flow-control is considered an antipattern, and Monads are considered an alternative to that practice.

There still is a need to adapt exception-laden code when wrapping them in monads, though.

You can work around this in a couple ways. The Try monad is probably the best approach.

Alternately, you can reraise Halt

def my_method
  yield Failiure('error code')
rescue Dry::Monads::Do::Halt
  raise
rescue => err
  Failure(err)
end

I can’t think of any reason why you would need to use yield inside a Try monad. The purpose is to act as a boundary between exceptional code and monadic code. Simply moving the yield outside of the Try block should work fine

yield Try { raise 'something went wrong' }.to_result

Some times, using yield is inconvenient for various reasons, and in those situations you can use the bind interface directly.

Dry::Monads::Do.bind Failure('error code')

Basically, your code is responsible for transforming what it is doing into a monadic type, and then you hand it off to yield. So for example, every Operation object that I use has this helper available:

# Utility helper to avoid .to_monad.or {} chaining
#
# @param [#to_monad] result, Dry::Monads::Result or object that responds to `to_monad`
# @param [Dry::Monads::Result::Failure, #to_proc, { :error => Symbol }] error
#
# @example With Yield
#   user = yield fetch_user.(id).or { |err| Failure[:not_found, err] }
#
# @example With Either
#   user = either fetch_user.(id), Failure(:not_found)
#   user = either fetch_user.(id), ->(err) { Failure[:not_found, err] }
#   user = either fetch_user.(ud), error: :not_found
#
# @raise [Dry::Monads::Do::Halt]
#
# @return the unwrapped successful value
def either(result, error)
  failure =
    case error
    in { error: error_name }
      proc { |err| Failure[error_name, err] }
    in T::Procable
      error
    else
      proc { error }
    end

  Dry::Monads::Do.bind result.to_monad.or(&failure)
end
1 Like