cypriss / mutations

Compose your business logic into commands that sanitize and validate input.
MIT License
1.39k stars 93 forks source link

Another alternative (and backwards-compatible) approach to Outcome class : Dry::Monads::Result #123

Open saverio-kantox opened 6 years ago

saverio-kantox commented 6 years ago

So there is this dry-rb/dry-monads library that has lots of goodies. One that is really useful for mutations is the Result monad, which has two subclasses Success and Failure.

I propose (and I can actually provide the PR) to replace Outcome with subclasses of Success and Failure, provided that:

With the previous additions, they are completely backwards-compatible, and it would be some 50 LOC.

Having Outcomes based on monads adds this optional behavior (the first one was asked in #102):


# result method with two args, provided by Dry::Monads::Result
outcome.result(
  ->(error) { puts "Error: #{error.inspect}" },
  ->(result) { puts "Result: #{result.inspect}" }
)

# `bind`, `fmap` and `or` chain other code or return the receiver, depending on success:

# bind returns the result of the block - for instance another outcome, or something else
successful_outcome.bind { |result| 1 }
# => 1
failed_outcome.bind { |result| 1 }
# => failed_outcome

# fmap wraps the result inside the same type
successful_outcome.fmap { |result| 1 }
# => Outcome::Success<value=1>
failed_outcome.fmap { |result| 1 }
# => failed_outcome

# `or` chain other code provided it's a failure:
successful_outcome.or { |result| 1 }
# => successful_outcome
failed_outcome.or { |result| 1 }
# => 1

# `or_fmap` chain other code provided it's a failure, and wraps in a success:
successful_outcome.or_fmap { |result| 1 }
# => successful_outcome
failed_outcome.or_fmap { |result| 1 }
# => Outcome::Success<value=1>

plus many more:

If Mutation::Command subclasses could additionally respond to call (by aliasing it to run), they would allow constructs like:


MyCommand.(inital_args)
  .bind(MyOtherCommand)
  .bind(StillAnotherCommand, extra: "params")

All without breaking current code depending on run, run! and Outcome.

saverio-kantox commented 6 years ago

This can be also a peer-dependency, so if Dry::Monads is loaded, the alternative implementation steps in.