Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/bolshakov/fear

Ruby port of some Scala's monads
https://github.com/bolshakov/fear

either monads option pattern-matching ruby try

Last synced: 7 days ago
JSON representation

Ruby port of some Scala's monads

Awesome Lists containing this project

README

        

# Fear
[![Gem Version](https://badge.fury.io/rb/fear.svg)](https://badge.fury.io/rb/fear)
[![Spec](https://github.com/bolshakov/fear/actions/workflows/spec.yml/badge.svg)](https://github.com/bolshakov/fear/actions/workflows/spec.yml)
[![Maintainability](https://api.codeclimate.com/v1/badges/01030620c59e9f40961b/maintainability)](https://codeclimate.com/github/bolshakov/fear/maintainability)
[![Coverage Status](https://coveralls.io/repos/github/bolshakov/fear/badge.svg?branch=master)](https://coveralls.io/github/bolshakov/fear?branch=master)

This gem provides `Option`, `Either`, and `Try` monads implemented an idiomatic way.
It is highly inspired by scala's implementation.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'fear'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install fear

## Usage

* [Option](#option-api-documentation)
* [Try](#try-api-documentation)
* [Either](#either-api-documentation)
* [Future](#future-api-documentation)
* [For composition](#for-composition-api-documentation)
* [Pattern Matching](#pattern-matching-api-documentation)

### Option ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option))

Represents optional (nullable) values. Instances of `Option` are either an instance of
`Some` or the object `None`.

The most idiomatic way to use an `Option` instance is to treat it as a collection

```ruby
name = Fear.option(params[:name])
upper = name.map(&:strip).select { |n| n.length != 0 }.map(&:upcase)
puts upper.get_or_else('')
```

This allows for sophisticated chaining of `Option` values without
having to check for the existence of a value.

A less-idiomatic way to use `Option` values is via pattern matching

```ruby
case Fear.option(params[:name])
in Fear::Some(name)
name.strip.upcase
in Fear::None
'No name value'
end
```

or manually checking for non emptiness

```ruby
name = Fear.option(params[:name])
if name.empty?
puts 'No name value'
else
puts name.strip.upcase
end
```

Alternatively, you can use camel-case factory methods `Fear::Option()`, `Fear::Some()` and `Fear::None` methods:

```ruby
Fear::Option(42) #=> #
Fear::Option(nil) #=> #

Fear::Some(42) #=> #
Fear::Some(nil) #=> #
Fear::None #=> #
```

#### Option#get_or_else

Returns the value from this `Some` or evaluates the given default argument if this is a `None`.

```ruby
Fear.some(42).get_or_else { 24/2 } #=> 42
Fear.none.get_or_else { 24/2 } #=> 12

Fear.some(42).get_or_else(12) #=> 42
Fear.none.get_or_else(12) #=> 12
```

#### Option#or_else

returns self `Some` or the given alternative if this is a `None`.

```ruby
Fear.some(42).or_else { Fear.some(21) } #=> Fear.some(42)
Fear.none.or_else { Fear.some(21) } #=> Fear.some(21)
Fear.none.or_else { None } #=> None
```

#### Option#include?

Checks if `Option` has an element that is equal (as determined by `==`) to given values.

```ruby
Fear.some(17).include?(17) #=> true
Fear.some(17).include?(7) #=> false
Fear.none.include?(17) #=> false
```

#### Option#each

Performs the given block if this is a `Some`.

```ruby
Fear.some(17).each { |value| puts value } #=> prints 17
Fear.none.each { |value| puts value } #=> does nothing
```

#### Option#map

Maps the given block to the value from this `Some` or returns self if this is a `None`

```ruby
Fear.some(42).map { |v| v/2 } #=> Fear.some(21)
Fear.none.map { |v| v/2 } #=> None
```

#### Option#flat_map

Returns the given block applied to the value from this `Some` or returns self if this is a `None`

```ruby
Fear.some(42).flat_map { |v| Fear.some(v/2) } #=> Fear.some(21)
Fear.none.flat_map { |v| Fear.some(v/2) } #=> None
```

#### Option#any?

Returns `false` if `None` or returns the result of the application of the given predicate to the `Some` value.

```ruby
Fear.some(12).any? { |v| v > 10 } #=> true
Fear.some(7).any? { |v| v > 10 } #=> false
Fear.none.any? { |v| v > 10 } #=> false
```

#### Option#select

Returns self if it is nonempty and applying the predicate to this `Option`'s value returns `true`. Otherwise,
return `None`.

```ruby
Fear.some(42).select { |v| v > 40 } #=> Fear.some(42)
Fear.some(42).select { |v| v < 40 } #=> None
Fear.none.select { |v| v < 40 } #=> None
```

#### Option#filter_map

Returns a new `Some` of truthy results (everything except `false` or `nil`) of
running the block or `None` otherwise.

```ruby
Fear.some(42).filter_map { |v| v/2 if v.even? } #=> Fear.some(21)
Fear.some(42).filter_map { |v| v/2 if v.odd? } #=> Fear.none
Fear.some(42).filter_map { |v| false } #=> Fear.none
Fear.none.filter_map { |v| v/2 } #=> Fear.none
```

#### Option#reject

Returns `Some` if applying the predicate to this `Option`'s value returns `false`. Otherwise, return `None`.

```ruby
Fear.some(42).reject { |v| v > 40 } #=> None
Fear.some(42).reject { |v| v < 40 } #=> Fear.some(42)
Fear.none.reject { |v| v < 40 } #=> None
```

#### Option#get

Not an idiomatic way of using Option at all. Returns values of raise `NoSuchElementError` error if option is empty.

#### Option#empty?

Returns `true` if the `Option` is `None`, `false` otherwise.

```ruby
Fear.some(42).empty? #=> false
Fear.none.empty? #=> true
```

#### Option#blank?

Returns `true` if the `Option` is `None`, `false` otherwise.

```ruby
Fear.some(42).blank? #=> false
Fear.none.blank? #=> true
```

#### Option#present?

Returns `false` if the `Option` is `None`, `true` otherwise.

```ruby
Fear.some(42).present? #=> true
Fear.none.present? #=> false
```

#### Option#zip

Returns a `Fear::Some` formed from this Option and another Option by combining the corresponding elements in a pair.
If either of the two options is empty, `Fear::None` is returned.

```ruby
Fear.some("foo").zip(Fear.some("bar")) #=> Fear.some(["foo", "bar"])
Fear.some("foo").zip(Fear.some("bar")) { |x, y| x + y } #=> Fear.some("foobar")
Fear.some("foo").zip(Fear.none) #=> Fear.none
Fear.none.zip(Fear.some("bar")) #=> Fear.none

```

@see https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala

### Try ([API Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Try))

The `Try` represents a computation that may either result
in an exception, or return a successfully computed value. Instances of `Try`,
are either an instance of `Success` or `Failure`.

For example, `Try` can be used to perform division on a
user-defined input, without the need to do explicit
exception-handling in all of the places that an exception
might occur.

```ruby
dividend = Fear.try { Integer(params[:dividend]) }
divisor = Fear.try { Integer(params[:divisor]) }
problem = dividend.flat_map { |x| divisor.map { |y| x / y } }

case problem
in Fear::Success(result)
puts "Result of #{dividend.get} / #{divisor.get} is: #{result}"
in Fear::Failure(ZeroDivisionError)
puts "Division by zero is not allowed"
in Fear::Failure(exception)
puts "You entered something wrong. Try again"
puts "Info from the exception: #{exception.message}"
end
```

An important property of `Try` shown in the above example is its
ability to _pipeline_, or chain, operations, catching exceptions
along the way. The `flat_map` and `map` combinators in the above
example each essentially pass off either their successfully completed
value, wrapped in the `Success` type for it to be further operated
upon by the next combinator in the chain, or the exception wrapped
in the `Failure` type usually to be simply passed on down the chain.
Combinators such as `recover_with` and `recover` are designed to provide some
type of default behavior in the case of failure.

*NOTE*: Only non-fatal exceptions are caught by the combinators on `Try`.
Serious system errors, on the other hand, will be thrown.

Alternatively, include you can use camel-case factory method `Fear::Try()`:

```ruby
Fear::Try { 4/0 } #=> #
Fear::Try { 4/2 } #=> #
```

#### Try#get_or_else

Returns the value from this `Success` or evaluates the given default argument if this is a `Failure`.

```ruby
Fear.success(42).get_or_else { 24/2 } #=> 42
Fear.failure(ArgumentError.new).get_or_else { 24/2 } #=> 12
```

#### Try#include?

Returns `true` if it has an element that is equal given values, `false` otherwise.

```ruby
Fear.success(17).include?(17) #=> true
Fear.success(17).include?(7) #=> false
Fear.failure(ArgumentError.new).include?(17) #=> false
```

#### Try#each

Performs the given block if this is a `Success`. If block raise an error,
then this method may raise an exception.

```ruby
Fear.success(17).each { |value| puts value } #=> prints 17
Fear.failure(ArgumentError.new).each { |value| puts value } #=> does nothing
```

#### Try#map

Maps the given block to the value from this `Success` or returns self if this is a `Failure`.

```ruby
Fear.success(42).map { |v| v/2 } #=> Fear.success(21)
Fear.failure(ArgumentError.new).map { |v| v/2 } #=> Fear.failure(ArgumentError.new)
```

#### Try#flat_map

Returns the given block applied to the value from this `Success`or returns self if this is a `Failure`.

```ruby
Fear.success(42).flat_map { |v| Fear.success(v/2) } #=> Fear.success(21)
Fear.failure(ArgumentError.new).flat_map { |v| Fear.success(v/2) } #=> Fear.failure(ArgumentError.new)
```

#### Try#to_option

Returns an `Some` containing the `Success` value or a `None` if this is a `Failure`.

```ruby
Fear.success(42).to_option #=> Fear.some(42)
Fear.failure(ArgumentError.new).to_option #=> None
```

#### Try#any?

Returns `false` if `Failure` or returns the result of the application of the given predicate to the `Success` value.

```ruby
Fear.success(12).any? { |v| v > 10 } #=> true
Fear.success(7).any? { |v| v > 10 } #=> false
Fear.failure(ArgumentError.new).any? { |v| v > 10 } #=> false
```

#### Try#success? and Try#failure?

```ruby
Fear.success(12).success? #=> true
Fear.success(12).failure? #=> true

Fear.failure(ArgumentError.new).success? #=> false
Fear.failure(ArgumentError.new).failure? #=> true
```

#### Try#get

Returns the value from this `Success` or raise the exception if this is a `Failure`.

```ruby
Fear.success(42).get #=> 42
Fear.failure(ArgumentError.new).get #=> ArgumentError: ArgumentError
```

#### Try#or_else

Returns self `Try` if it's a `Success` or the given alternative if this is a `Failure`.

```ruby
Fear.success(42).or_else { Fear.success(-1) } #=> Fear.success(42)
Fear.failure(ArgumentError.new).or_else { Fear.success(-1) } #=> Fear.success(-1)
Fear.failure(ArgumentError.new).or_else { Fear.try { 1/0 } } #=> Fear.failure(ZeroDivisionError.new('divided by 0'))
```

#### Try#flatten

Transforms a nested `Try`, ie, a `Success` of `Success`, into an un-nested `Try`, ie, a `Success`.

```ruby
Fear.success(42).flatten #=> Fear.success(42)
Fear.success(Fear.success(42)).flatten #=> Fear.success(42)
Fear.success(Fear.failure(ArgumentError.new)).flatten #=> Fear.failure(ArgumentError.new)
Fear.failure(ArgumentError.new).flatten { -1 } #=> Fear.failure(ArgumentError.new)
```

#### Try#select

Converts this to a `Failure` if the predicate is not satisfied.

```ruby
Fear.success(42).select { |v| v > 40 }
#=> Fear.success(42)
Fear.success(42).select { |v| v < 40 }
#=> Fear.failure(Fear::NoSuchElementError.new("Predicate does not hold for 42"))
Fear.failure(ArgumentError.new).select { |v| v < 40 }
#=> Fear.failure(ArgumentError.new)
```

#### Recovering from errors

There are two ways to recover from the error. `Try#recover_with` method is like `flat_map` for the exception. And
you can pattern match against the error!

```ruby
Fear.success(42).recover_with do |m|
m.case(ZeroDivisionError) { Fear.success(0) }
end #=> Fear.success(42)

Fear.failure(ArgumentError.new).recover_with do |m|
m.case(ZeroDivisionError) { Fear.success(0) }
m.case(ArgumentError) { |error| Fear.success(error.class.name) }
end #=> Fear.success('ArgumentError')
```

If the block raises error, this new error returned as an result

```ruby
Fear.failure(ArgumentError.new).recover_with do
raise
end #=> Fear.failure(RuntimeError)
```

The second possibility for recovery is `Try#recover` method. It is like `map` for the exception. And it's also heavely
relies on pattern matching.

```ruby
Fear.success(42).recover do |m|
m.case(&:message)
end #=> Fear.success(42)

Fear.failure(ArgumentError.new).recover do |m|
m.case(ZeroDivisionError) { 0 }
m.case(&:message)
end #=> Fear.success('ArgumentError')
```

If the block raises an error, this new error returned as an result

```ruby
Fear.failure(ArgumentError.new).recover do |m|
raise
end #=> Fear.failure(RuntimeError)
```

#### Try#to_either

Returns `Left` with exception if this is a `Failure`, otherwise returns `Right` with `Success` value.

```ruby
Fear.success(42).to_either #=> Fear.right(42)
Fear.failure(ArgumentError.new).to_either #=> Fear.left(ArgumentError.new)
```

### Either ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Either))

Represents a value of one of two possible types (a disjoint union.)
An instance of `Either` is either an instance of `Left` or `Right`.

A common use of `Either` is as an alternative to `Option` for dealing
with possible missing values. In this usage, `None` is replaced
with a `Left` which can contain useful information.
`Right` takes the place of `Some`. Convention dictates
that `Left` is used for failure and `Right` is used for Right.

For example, you could use `Either` to `#select_or_else` whether a
received input is a +String+ or an +Fixnum+.

```ruby
in = Readline.readline('Type Either a string or an Int: ', true)
result = begin
Fear.right(Integer(in))
rescue ArgumentError
Fear.left(in)
end

case result
in Fear::Right(x)
"You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}"
in Fear::Left(x)
"You passed me the String: #{x}"
end
```

Either is right-biased, which means that `Right` is assumed to be the default case to
operate on. If it is `Left`, operations like `#map`, `#flat_map`, ... return the `Left` value
unchanged.

Alternatively, you can use camel-case factory methods `Fear::Left()`, and `Fear::Right()`:

```ruby
Fear::Left(42) #=> #
Fear::Right(42) #=> #
```

#### Either#get_or_else

Returns the value from this `Right` or evaluates the given default argument if this is a `Left`.

```ruby
Fear.right(42).get_or_else { 24/2 } #=> 42
Fear.left('undefined').get_or_else { 24/2 } #=> 12

Fear.right(42).get_or_else(12) #=> 42
Fear.left('undefined').get_or_else(12) #=> 12
```

#### Either#or_else

Returns self `Right` or the given alternative if this is a `Left`.

```ruby
Fear.right(42).or_else { Fear.right(21) } #=> Fear.right(42)
Fear.left('unknown').or_else { Fear.right(21) } #=> Fear.right(21)
Fear.left('unknown').or_else { Fear.left('empty') } #=> Fear.left('empty')
```

#### Either#include?

Returns `true` if `Right` has an element that is equal to given value, `false` otherwise.

```ruby
Fear.right(17).include?(17) #=> true
Fear.right(17).include?(7) #=> false
Fear.left('undefined').include?(17) #=> false
```

#### Either#each

Performs the given block if this is a `Right`.

```ruby
Fear.right(17).each { |value| puts value } #=> prints 17
Fear.left('undefined').each { |value| puts value } #=> does nothing
```

#### Either#map

Maps the given block to the value from this `Right` or returns self if this is a `Left`.

```ruby
Fear.right(42).map { |v| v/2 } #=> Fear.right(21)
Fear.left('undefined').map { |v| v/2 } #=> Fear.left('undefined')
```

#### Either#flat_map

Returns the given block applied to the value from this `Right` or returns self if this is a `Left`.

```ruby
Fear.right(42).flat_map { |v| Fear.right(v/2) } #=> Fear.right(21)
Fear.left('undefined').flat_map { |v| Fear.right(v/2) } #=> Fear.left('undefined')
```

#### Either#to_option

Returns an `Some` containing the `Right` value or a `None` if this is a `Left`.

```ruby
Fear.right(42).to_option #=> Fear.some(42)
Fear.left('undefined').to_option #=> Fear::None
```

#### Either#any?

Returns `false` if `Left` or returns the result of the application of the given predicate to the `Right` value.

```ruby
Fear.right(12).any? { |v| v > 10 } #=> true
Fear.right(7).any? { |v| v > 10 } #=> false
Fear.left('undefined').any? { |v| v > 10 } #=> false
```

#### Either#right?, Either#success?

Returns `true` if this is a `Right`, `false` otherwise.

```ruby
Fear.right(42).right? #=> true
Fear.left('err').right? #=> false
```

#### Either#left?, Either#failure?

Returns `true` if this is a `Left`, `false` otherwise.

```ruby
Fear.right(42).left? #=> false
Fear.left('err').left? #=> true
```

#### Either#select_or_else

Returns `Left` of the default if the given predicate does not hold for the right value, otherwise,
returns `Right`.

```ruby
Fear.right(12).select_or_else(-1, &:even?) #=> Fear.right(12)
Fear.right(7).select_or_else(-1, &:even?) #=> Fear.left(-1)
Fear.left(12).select_or_else(-1, &:even?) #=> Fear.left(12)
Fear.left(12).select_or_else(-> { -1 }, &:even?) #=> Fear.left(12)
```

#### Either#select

Returns `Left` of value if the given predicate does not hold for the right value, otherwise, returns `Right`.

```ruby
Fear.right(12).select(&:even?) #=> Fear.right(12)
Fear.right(7).select(&:even?) #=> Fear.left(7)
Fear.left(12).select(&:even?) #=> Fear.left(12)
Fear.left(7).select(&:even?) #=> Fear.left(7)
```

#### Either#reject

Returns `Left` of value if the given predicate holds for the right value, otherwise, returns `Right`.

```ruby
Fear.right(12).reject(&:even?) #=> Fear.left(12)
Fear.right(7).reject(&:even?) #=> Fear.right(7)
Fear.left(12).reject(&:even?) #=> Fear.left(12)
Fear.left(7).reject(&:even?) #=> Fear.left(7)
```

#### Either#swap

If this is a `Left`, then return the left value in `Right` or vice versa.

```ruby
Fear.left('left').swap #=> Fear.right('left')
Fear.right('right').swap #=> Fear.left('left')
```

#### Either#left

Projects this `Fear::Either` as a `Fear::Left`.
This allows performing right-biased operation of the left
side of the `Fear::Either`.

```ruby
Fear.left(42).left.map(&:succ) #=> Fear.left(43)
Fear.right(42).left.map(&:succ) #=> Fear.left(42)

Fear.left(42).left.select(&:even?) #=> Fear.left(42)
Fear.right(42).left.select(&:odd?) #=> Fear.right(42)
```

#### Either#reduce

Applies `reduce_left` if this is a `Left` or `reduce_right` if this is a `Right`.

```ruby
result = possibly_failing_operation()
log(
result.reduce(
->(ex) { "Operation failed with #{ex}" },
->(v) { "Operation produced value: #{v}" },
)
)
```

#### Either#join_right

Joins an `Either` through `Right`. This method requires that the right side of this `Either` is itself an
`Either` type. This method, and `join_left`, are analogous to `Option#flatten`

```ruby
Fear.right(Fear.right(12)).join_right #=> Fear.right(12)
Fear.right(Fear.left("flower")).join_right #=> Fear.left("flower")
Fear.left("flower").join_right #=> Fear.left("flower")
Fear.left(Fear.right("flower")).join_right #=> Fear.left(Fear.right("flower"))
```

#### Either#join_left

Joins an `Either` through `Left`. This method requires that the left side of this `Either` is itself an
`Either` type. This method, and `join_right`, are analogous to `Option#flatten`

```ruby
Fear.left(Fear.right("flower")).join_left #=> Fear.right("flower")
Fear.left(Fear.left(12)).join_left #=> Fear.left(12)
Fear.right("daisy").join_left #=> Fear.right("daisy")
Fear.right(Fear.left("daisy")).join_left #=> Fear.right(Fear.left("daisy"))
```

### Future ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Future))

Asynchronous computations that yield futures are created
with the `Fear.future` call

```ruby
success = "Hello"
f = Fear.future { success + ' future!' }
f.on_success do |result|
puts result
end
```

Multiple callbacks may be registered; there is no guarantee
that they will be executed in a particular order.

The future may contain an exception and this means
that the future failed. Futures obtained through combinators
have the same error as the future they were obtained from.

```ruby
f = Fear.future { 5 }
g = Fear.future { 3 }

f.flat_map do |x|
g.map { |y| x + y }
end
```

Futures use [Concurrent::Promise](https://ruby-concurrency.github.io/concurrent-ruby/1.1.5/Concurrent/Promise.html#constructor_details)
under the hood. `Fear.future` accepts optional configuration Hash passed directly to underlying promise. For example,
run it on custom thread pool.

```ruby
require 'open-uri'
pool = Concurrent::FixedThreadPool.new(5)
future = Fear.future(executor: pool) { open('https://example.com/') }
future.map(&:read).each do |body|
puts "#{body}"
end

```

Futures support common monadic operations -- `#map`, `#flat_map`, and `#each`. That's why it's possible to combine them
using `Fear.for`, It returns the Future containing Success of `5 + 3` eventually.

```ruby
f = Fear.future { 5 }
g = Fear.future { 3 }

Fear.for(f, g) do |x, y|
x + y
end
```

Future goes with the number of callbacks. You can register several callbacks, but the order of execution isn't guaranteed

```ruby
f = Fear.future { ... } # call external service
f.on_success do |result|
# handle service response
end

f.on_failure do |error|
# handle exception
end
```

or you can wait for Future completion

```ruby
f.on_complete do |result|
result.match do |m|
m.success { |value| ... }
m.failure { |error| ... }
end
end
```

In sake of convenience `#on_success` callback aliased as `#each`.

It's possible to get future value directly, but since it may be incomplete, `#value` method returns `Fear::Option`. So,
there are three possible responses:

```ruby
future.value #=>
# Fear::Some #=> future completed with value
# Fear::Some #=> future completed with error
# Fear::None #=> future not yet completed
```

There is a variety of methods to manipulate with futures.

```ruby
Fear.future { open('http://example.com').read }
.transform(
->(value) { ... },
->(error) { ... },
)

future = Fear.future { 5 }
future.select(&:odd?) # evaluates to Fear.success(5)
future.select(&:even?) # evaluates to Fear.error(NoSuchElementError)
```

You can zip several asynchronous computations into one future. For you can call two external services and
then zip the results into one future containing array of both responses:

```ruby
future1 = Fear.future { call_service1 }
future1 = Fear.future { call_service2 }
future1.zip(future2)
```

It returns the same result as `Fear.future { [call_service1, call_service2] }`, but the first version performs
two simultaneous calls.

There are two ways to recover from failure. `Future#recover` is live `#map` for failures:

```ruby
Fear.future { 2 / 0 }.recover do |m|
m.case(ZeroDivisionError) { 0 }
end #=> returns new future of Fear.success(0)
```

If the future resolved to success or recovery matcher did not matched, it returns the future `Fear::Failure`.

The second option is `Future#fallback_to` method. It allows to fallback to result of another future in case of failure

```ruby
future = Fear.future { fail 'error' }
fallback = Fear.future { 5 }
future.fallback_to(fallback) # evaluates to 5
```

You can run callbacks in specific order using `#and_then` method:

```ruby
f = Fear.future { 5 }
f.and_then do
fail 'runtime error'
end.and_then do |m|
m.success { |value| puts value } # it evaluates this branch
m.failure { |error| puts error.massage }
end
```

#### Testing future values

Sometimes it may be helpful to await for future completion. You can await either future,
or result. Don't forget to pass timeout in seconds:

```ruby
future = Fear.future { 42 }

Fear::Await.result(future, 3) #=> 42

Fear::Await.ready(future, 3) #=> Fear::Future.successful(42)
```

### For composition ([API Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/ForApi))

Provides syntactic sugar for composition of multiple monadic operations.
It supports two such operations - `flat_map` and `map`. Any class providing them
is supported by `For`.

```ruby
Fear.for(Fear.some(2), Fear.some(3)) do |a, b|
a * b
end #=> Fear.some(6)
```

If one of operands is None, the result is None

```ruby
Fear.for(Fear.some(2), None) do |a, b|
a * b
end #=> None

Fear.for(None, Fear.some(2)) do |a, b|
a * b
end #=> None
```

Lets look at first example:

```ruby
Fear.for(Fear.some(2), None) do |a, b|
a * b
end #=> None
```

it is translated to:

```ruby
Fear.some(2).flat_map do |a|
Fear.some(3).map do |b|
a * b
end
end
```

It works with arrays as well

```ruby
Fear.for([1, 2], [2, 3], [3, 4]) { |a, b, c| a * b * c }
#=> [6, 8, 9, 12, 12, 16, 18, 24]

```

it is translated to:

```ruby
[1, 2].flat_map do |a|
[2, 3].flat_map do |b|
[3, 4].map do |c|
a * b * c
end
end
end
```

If you pass lambda as a variable value, it would be evaluated
only on demand.

```ruby
Fear.for(proc { None }, proc { raise 'kaboom' } ) do |a, b|
a * b
end #=> None
```

It does not fail since `b` is not evaluated.
You can refer to previously defined variables from within lambdas.

```ruby
maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>

Fear.for(maybe_user, ->(user) { user.birthday }) do |user, birthday|
"#{user.name} was born on #{birthday}"
end #=> Fear.some('Paul was born on 1987-06-17')
```

### Pattern Matching ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/PatternMatchingApi))

#### Syntax

To pattern match against a value, use `Fear.match` function, and provide at least one case clause:

```ruby
x = Random.rand(10)

Fear.match(x) do |m|
m.case(0) { 'zero' }
m.case(1) { 'one' }
m.case(2) { 'two' }
m.else { 'many' }
end
```

The `x` above is a random integer from 0 to 10. The last clause `else` is a “catch all” case
for anything other than `0`, `1`, and `2`. If you want to ensure that an Integer value is passed,
matching against type available:

```ruby
Fear.match(x) do |m|
m.case(Integer, 0) { 'zero' }
m.case(Integer, 1) { 'one' }
m.case(Integer, 2) { 'two' }
m.case(Integer) { 'many' }
end
```

Providing something other than Integer will raise `Fear::MatchError` error.

#### Pattern guards

You can use whatever you want as a pattern guard, if it respond to `#===` method to to make cases more specific.

```ruby
m.case(20..40) { |m| "#{m} is within range" }
m.case(->(x) { x > 10}) { |m| "#{m} is greater than 10" }
m.case(:even?.to_proc) { |x| "#{x} is even" }
m.case(:odd?.to_proc) { |x| "#{x} is odd" }
```

It's also possible to create matcher and use it several times:

```ruby
matcher = Fear.matcher do |m|
m.case(Integer) { |n| "#{n} is a number" }
m.case(String) { |n| "#{n} is a string" }
m.else { |n| "#{n} is a #{n.class}" }
end

matcher.(42) #=> "42 is a number"
matcher.(10..20) #=> "10..20 is a Range"
```

#### How to debug pattern extractors?

You can build pattern manually and ask for failure reason:

```ruby
Fear['Some([:err, 444])'].failure_reason(Fear.some([:err, 445]))
# =>
Expected `445` to match:
Some([:err, 444])
~~~~~~~~~~~~^
```

by the way you can also match against such pattern

```ruby
Fear['Some([:err, 444])'] === Fear.some([:err, 445]) #=> false
Fear['Some([:err, 444])'] === Fear.some([:err, 445]) #=> true
```

#### More examples

Factorial using pattern matching

```ruby
factorial = Fear.matcher do |m|
m.case(->(n) { n <= 1} ) { 1 }
m.else { |n| n * factorial.(n - 1) }
end

factorial.(10) #=> 3628800
```

Fibonacci number

```ruby
fibonacci = Fear.matcher do |m|
m.case(0) { 0 }
m.case(1) { 1 }
m.case(->(n) { n > 1}) { |n| fibonacci.(n - 1) + fibonacci.(n - 2) }
end

fibonacci.(10) #=> 55
```

Binary tree set implemented using pattern matching https://gist.github.com/bolshakov/3c51bbf7be95066d55d6d1ac8c605a1d

#### Monads pattern matching

You can use `Option#match`, `Either#match`, and `Try#match` method. It performs matching not
only on container itself, but on enclosed value as well.

Pattern match against an `Option`

```ruby
Fear.some(42).match do |m|
m.some { |x| x * 2 }
m.none { 'none' }
end #=> 84
```

pattern match on enclosed value

```ruby
Fear.some(41).match do |m|
m.some(:even?.to_proc) { |x| x / 2 }
m.some(:odd?.to_proc, ->(v) { v > 0 }) { |x| x * 2 }
m.none { 'none' }
end #=> 82
```

it raises `Fear::MatchError` error if nothing matched. To avoid exception, you can pass `#else` branch

```ruby
Fear.some(42).match do |m|
m.some(:odd?.to_proc) { |x| x * 2 }
m.else { 'nothing' }
end #=> nothing
```

Pattern matching works the similar way for `Either` and `Try` monads.

In sake of performance, you may want to generate pattern matching function and reuse it multiple times:

```ruby
matcher = Fear::Option.matcher do |m|
m.some(42) { 'Yep' }
m.some { 'Nope' }
m.none { 'Error' }
end

matcher.(Fear.some(42)) #=> 'Yep'
matcher.(Fear.some(40)) #=> 'Nope'
```

#### Under the hood

Pattern matcher is a combination of partial functions wrapped into nice DSL. Every partial function
defined on domain described with a guard.

```ruby
pf = Fear.case(Integer) { |x| x / 2 }
pf.defined_at?(4) #=> true
pf.defined_at?('Foo') #=> false
pf.call('Foo') #=> raises Fear::MatchError
pf.call_or_else('Foo') { 'not a number' } #=> 'not a number'
pf.call_or_else(4) { 'not a number' } #=> 2
pf.lift.call('Foo') #=> Fear::None
pf.lift.call(4) #=> Fear.some(2)
```

It uses `#===` method under the hood, so you can pass:

* Class to check kind of an object.
* Lambda to evaluate it against an object.
* Any literal, like `4`, `"Foobar"`, `:not_found` etc.
* Qo matcher -- `m.case(Qo[name: 'John']) { .... }`

Partial functions may be combined with each other:

```ruby
is_even = Fear.case(->(arg) { arg % 2 == 0}) { |arg| "#{arg} is even" }
is_odd = Fear.case(->(arg) { arg % 2 == 1}) { |arg| "#{arg} is odd" }

(10..20).map(&is_even.or_else(is_odd))

to_integer = Fear.case(String, &:to_i)
integer_two_times = Fear.case(Integer) { |x| x * 2 }

two_times = to_integer.and_then(integer_two_times).or_else(integer_two_times)
two_times.(4) #=> 8
two_times.('42') #=> 84
```

Since matcher is just a syntactic sugar for partial functions, you can combine matchers with partial
functions and each other.

```ruby
handle_numbers = Fear.case(Integer, &:itself).and_then(
Fear.matcher do |m|
m.case(0) { 'zero' }
m.case(->(n) { n < 10 }) { 'smaller than ten' }
m.case(->(n) { n > 10 }) { 'bigger than ten' }
end
)

handle_strings = Fear.case(String, &:itself).and_then(
Fear.matcher do |m|
m.case('zero') { 0 }
m.case('one') { 1 }
m.else { 'unexpected' }
end
)

handle = handle_numbers.or_else(handle_strings)
handle.(0) #=> 'zero'
handle.(12) #=> 'bigger than ten'
handle.('one') #=> 1
```

### Native pattern-matching

Starting from ruby 2.7 you can use native pattern matching capabilities:

```ruby
case Fear.some(42)
in Fear::Some(x)
x * 2
in Fear::None
'none'
end #=> 84

case Fear.some(41)
in Fear::Some(x) if x.even?
x / 2
in Fear::Some(x) if x.odd? && x > 0
x * 2
in Fear::None
'none'
end #=> 82

case Fear.some(42)
in Fear::Some(x) if x.odd?
x * 2
else
'nothing'
end #=> nothing
```

It's possible to pattern match against Fear::Either and Fear::Try as well:

```ruby
case either
in Fear::Right(Integer | String => x)
"integer or string: #{x}"
in Fear::Left(String => error_code) if error_code = :not_found
'not found'
end
```

```ruby
case Fear.try { 10 / x }
in Fear::Failure(ZeroDivisionError)
# ..
in Fear::Success(x)
# ..
end
```

### Dry-Types integration

To use `Fear::Option` as optional type for `Dry::Types` use the [dry-types-fear] gem.

## Testing

To simplify testing, you may use [fear-rspec](https://github.com/bolshakov/fear-rspec) gem. It
provides a bunch of rspec matchers.

## Contributing

1. Fork it ( https://github.com/bolshakov/fear/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

## Alternatives

* [algebrick](https://github.com/pitr-ch/algebrick)
* [deterministic](https://github.com/pzol/deterministic)
* [dry-monads](https://github.com/dry-rb/dry-monads)
* [kleisli](https://github.com/txus/kleisli)
* [maybe](https://github.com/bhb/maybe)
* [ruby-possibly](https://github.com/rap1ds/ruby-possibly)
* [rumonade](https://github.com/ms-ati/rumonade)

[dry-types-fear]: https://github.com/bolshakov/dry-types-fear