module Flows::Result::Do
Do-notation for Result
Objects.
This functionality aims to simplify common control flow pattern: when you have to stop execution on a first failure and return this failure. Do
Notation inspired by [Do Notation in dry-rb](dry-rb.org/gems/dry-monads/1.3/do-notation/) and [Haskell do keyword](wiki.haskell.org/Keywords#do).
Sometimes you have to write something like this:
class Something include Flows::Result::Helpers def perform user_result = fetch_user return user_result if user_result.err? data_result = fetch_data return data_result if data_result.err? calculation_result = calculation(user_result.unwrap[:user], data_result.unwrap) return calculation_result if user_result.err? ok(data: calculation_result.unwrap[:some_field]) end private def fetch_user # returns Ok or Err end def fetch_data # returns Ok or Err end def calculation(_user, _data) # returns Ok or Err end end
The main idea of the code above is to stop method execution and return failed Result
Object if one of the sub-operations is failed. At the moment of failure.
By using Do
Notation feature you may rewrite it like this:
class SomethingWithDoNotation include Flows::Result::Helpers extend Flows::Result::Do # enable Do Notation do_notation(:perform) # changes behaviour of `yield` in this method def perform user = yield(fetch_user)[:user] # yield here returns array of one element data = yield fetch_data # yield here returns a Hash ok( data: yield(calculation(user, data))[:some_field] ) end # private method definitions end
`do_notation(:perform)` makes some wrapping here and allows you to use `yield` inside the `perform` method in a non standard way: to unpack results or instantly leave a method if a failed result was provided.
## How to use it
First of all, you have to include `Flows::Result::Do` mixin into your class or module. It adds `do_notation` class method. `do_notation` accepts method name as an argument and changes behaviour of `yield` inside this method. By the way, when you are using `do_notation` you cannot pass a block to modified method anymore.
class MyClass extend Flows::Result::Do do_notation(:my_method_1) def my_method_1 # some code end do_notation(:my_method_2) def my_method_2 # some code end end
`yield` in such methods is working by the following rules:
ok_result = Flows::Result::Ok.new(a: 1, b: 2) err_result = Flows::Result::Err.new(x: 1, y: 2) # the following three lines are equivalent yield(ok_result) ok_result.unwrap { a: 1, b: 2 } # the following three lines are equivalent yield(:a, :b, ok_result) ok_result.unwrap.values_at(:a, :b) [1, 2] # the following three lines are equivalent return err_result yield(err_result) yield(:x, :y, err_result)
As you may see, `yield` has two forms of usage:
-
`yield(result_value)` - returns unwrapped data Hash for successful results or, in case of failed result, stops method execution and returns failed `result_value` as a method result.
-
`yield(*keys, result_value)` - returns unwrapped data under provided keys as Array for successful results or, in case of failed result, stops method execution and returns failed `result_value` as a method result.
## How it works
Under the hood `Flows::Result::Do` creates a module and prepends it to your class or module. Invoking `do_notation(:method_name)` adds special wrapper method to the prepended module. So, when you perform call to `YourClassOrModule#method_name` - you're executing wrapper in the prepended module.
Constants
- MOD_VAR_NAME
- SingletonVarsSetup
Public Instance Methods
# File lib/flows/result/do.rb, line 165 def do_notation(method_name) prepended_mod = Util.fetch_and_prepend_module(self) Util.define_wrapper(prepended_mod, method_name) end