Getting 'dry' starting with monads ... a question


#1

As a not young/not old programmer many of these concepts are really new and it takes time to wrap one’s head around both how to use them and more interestingly why! The whole “dry” naming of this suite of gems has made me come back over and over to see how I can leverage this work.

In my case Javascript promises have have made the monad pattern much more familiar and I am seeing and really liking what I see. In fact I’ve been using my own form of a ‘monad’ for a long time although not nearly as elegant.

Here’s my question … I have a method that does work that will either succeed or fail. I am using the Result monad that allows the method to provide a Success or Failure result. So the calling code, a rails controller in this case, currently looks like:

my_object.hard_work( inputs ).bind do |reward|
   render json: reward, status: :ok 
end .or do |too_bad|
   render json: too_bad, status: :service_unavailable 
end

I my question here is the choice of ‘bind’ and ‘or’ as the methods names. I would think this would be more interesting:

my_object.hard_work( inputs ).successful do |reward|
   render json: reward, status: :ok 
end .failure do |too_bad|
   render json: too_bad, status: :service_unavailable 
end

I’m sure I’ve missed something here because the first case just doesn’t feel as satisfying as it should.

Could I get a pointer or two? I suspect that there many others asking similar questions … I hope!

Many thanks for all the hard work on dry.rb

P.S. As I think a bit further … is there a form that eliminates the need for block arguments?

my_object.hard_work( inputs ).successful do
   render json: successful_value, status: :ok 
end .failure do
   render json: failure_value, status: :service_unavailable 
end

i.e. “successful_value” and “failure_value” are provided to the programmer by default

And then stylistically I might even prefer the following:

my_object.hard_work( inputs ).successful {
   render json: successful_value, status: :ok 
}.failure { 
   render json: failure_value, status: :service_unavailable 
}

I find these last two forms very satisfying - so how do I get here? This is what I might be missing.


#2

Sounds like you might be interested in dry-matcher’s result matcher. See https://dry-rb.org/gems/dry-matcher/result-matcher/ for an intro, and https://dry-rb.org/gems/dry-matcher/class-enhancement/ for how to build the matcher API into your classes.


#3

FYI bind originally comes from Haskell (I think). Conceptually, it’s not about matching the result but for chaining functions (blocks in this case). As Tim mentioned, dry-matcher works better for this purpose. Another option is using case https://dry-rb.org/gems/dry-monads/1.0/case-equality/


#4

The place this becomes useful is if you have a second operation that takes the (successful) result of the first, but can itself fail:

my_object.hard_work(inputs).bind do |reward|
  my_object.do_more_work(reward)
.end .bind do |cake|
  render ...
.end

The dry-monads result is itself an object, and so you could pass it to a helper function

def render_result(result):
  if result.success?
    render json: result.value!, status: ok
  else
    render json: result.failure, status: :service_unavailable
  end
end

result = my_object.hard_work(inputs)
render_result(result)

You can also use the generic monadic syntax (especially .bind) with anything in dry-monadsMaybe where the failure case doesn’t have an error message attached to it, List where there could be multiple results – but the failure case is pretty specific to Result.