Concatenating the successful result of several Result monads

I am trying to figure out a nice way to concatenate the result of several Result monads, if they’re successful, to avoid horizontal nesting like this:

first.bind do |first_result|
  second(first_result).bind do |second_result|
    third(second_result).bind do |third_result|
      fourth(third_result, second_result).bind do |fourth_result|
         Success([first_result, second_result, third_result, fourth_result])

So that I in some way could end up with code looking a little cleaner, like so

  .bind { |first_result| second(first_result) }
  .bind { |second_result| third(second_result) }
  .bind { |third_result| fourth(third_result, second_result) }

Imagine that you have a service object whose responsibility it is to create several objects or fail entirely. Those objects should in turn be available to whoever calls the result.

Maybe this isn’t entirely related to the library itself but rather a design pattern/choice that I’m missing.

Let’s say the CreateProjectService is responsible for creating an Project, PriceEstimation, CompabilityMatrix, and SuccessEstimation. They have to be created in sequence as each entity is dependent on the previous entities (plural) created.

How does one go about writing this code without ending up without resorting to deep horizontal nesting?

It sounds like a job for do notation which was added in the recent release. The docs are not there yet (will be in a week I hope) but you can check out the changelog and ask for details here or on the gitter channel

I pushed 1.0.0.beta2 earlier this week so you can play around already.

1 Like

Thanks! This looks brilliant and exactly what I’m looking for!

One question: is there a way to use something explicit over specifying the method to augment with include Dry::Monads::Do.for(:call)? Something like

   Dry::Monads::Do.for do
      yield Success('yay')
      yield None

It is not what currently supported and, unfortunately, can’t be supported in Ruby because yield seeks for an enclosing method. However, something like this seems doable

Dry::Monads::Do do |unwrap|
  foo = unwrap.(m1)
  bar = unwrap.(m2)
  Success(foo + bar)

I could implement it but only after releasing 1.0 (complete docs are yet to be written but we’re close).

Gotcha. I was mostly thinking that it would be easier to explain to people what’s happening without the includes “magic”. I’ll wait for 1.0 before starting to implement this stuff in our codebase. Thanks a lot for the help!