v1.0.0
Added
-
Now you can use pattern matching against monads and your own values. See documentation.
Fear.some(42).match do |m| m.some { |x| x * 2 } m.none { 'none' } end #=> 84 x = Random.rand(10) Fear.match(x) do |m| m.case(0) { 'zero' } m.case(1) { 'one' } m.case(2) { 'two' } m.else(Integer, ->(n) { n > 2} ) { 'many' } end
Despite of standard case statement, pattern matching raises
Fear::MatchError
error if nothing was matched. Another interesting property is reusability. Matcher behaves like a function defined on a subset of all possible inputs. Look at recursive factorial definition:factorial = Fear.matcher do |m| m.case(->(n) { n <= 1} ) { 1 } m.else { |n| n * factorial.(n - 1) } end factorial.(10) #=> 3628800
You can compose several matchers together using
#and_then
and#or_else
methods: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
To avoid raising error, you use either
#lift
method or#call_or_else
. Lets look at the following fibonnaci number calculator:fibonnaci = Fear.matcher do |m| m.case(0) { 0 } m.case(1) { 1 } m.case(->(n) { n > 1}) { |n| fibonnaci.(n - 1) + fibonnaci.(n - 2) } end fibonnaci.(10) #=> 55 fibonnaci.(-1) #=> raises Fear::MatchError fibonnaci.lift.(-1) #=> Fear::None fibonnaci.lift.(10) #=> Fear::Some.new(55) fibonnaci.call_or_else(-1) { 'nothing' } #=> 'nothing' fibonnaci.call_or_else(10) { 'nothing' } #=> 55
-
Pattern extraction added. See documentation
It enables special syntax to match against pattern and extract values from that pattern at the same time. For example the following pattern matches an array starting from1
and captures its tail:matcher = Fear.matcher do |m| m.xcase('[1, *tail]') { |tail:| tail } end matcher.([1,2,3]) #=> [2,3] matcher.([2,3]) #=> raises MatchError
_
matches any value. Thus, the following pattern matches[1, 2, 3]
,[1, 'foo', 3]
, etc.matcher = Fear.matcher do |m| m.xcase('[1, _, 3]') { # ... } end
This syntax allows to match and extract deeply nested structures
matcher = Fear.matcher do |m| m.xcase('[["status", first_status], 4, *tail]') { |first_status:, tail: |.. } end matcher.([['status', 400], 4, 5, 6]) #=> yields block with `{first_status: 400, tail: [5,6]}`
It's also possible to extract custom data structures. Documentation has detailed explanation how to implement own extractor.
Fear has several built-in reference extractors:
matcher = Fear.matcher do |m| m.xcase('Date(year, 2, 29)', ->(year:) { year < 2000 }) do |year:| "#{year} is a leap year before Millennium" end m.xcase('Date(year, 2, 29)') do |year:| "#{year} is a leap year after Millennium" end m.case(Date) do |date| "#{date.year} is not a leap year" end end matcher.(Date.new(1996,02,29)) #=> "1996 is a leap year before Millennium" matcher.(Date.new(2004,02,29)) #=> "1996 is a leap year after Millennium" matcher.(Date.new(2003,01,24)) #=> "2003 is not a leap year"
-
All monads got
#match
and.matcher
method to match against contained values or build reusable matcher: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 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'
-
Fear::Future
was deleted long time ago, but now it's back. It's implemented on top ofconcurrent-ruby
gem and provides monadic interface for asynchronous computations. Its API inspired by future implementation in Scala, but with ruby flavor. See API Documentationsuccess = "Hello" f = Fear.future { success + ' future!' } f.on_success do |result| puts result end
The simplest way to wait for several futures to complete
Fear.for(Fear.future { 5 }, Fear.future { 3 }) do |x, y| x + y end #=> eventually will be 8
Since Futures use Concurrent::Promise under the hood.
Fear.future
accepts optional configuration Hash passed directly to underlying promise. For example, run it on custom thread pool.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
-
A bunch of factory method added to build monads without mixin a module:
Fear.some(value)
Fear.option(value_or_nil)
Fear.none
Fear.left(value)
Fear.right(value)
Fear.try(&block)
Fear.success(value)
Fear.failure(error)
Fear.for(*monads, &block)
Breaking
-
Support for ruby 2.3.7 was dropped.
-
Fear::None
is singleton now and the only instance ofFear::NoneClass
. -
Fear.for
syntax changed. Now it accepts a list of monads (previously hash)Fear.for(Fear.some(2), Fear.some(3)) do |a, b| a * b end #=> Fear.some(6) Fear.for(Fear.some(2), Fear.none) do |a, b| a * b end #=> Fear::None
It's internal implementation also changed -- less metaprogramming magic, faster execution
-
#to_a
method removed. -
Fear::Done
was renamed toFear::Unit
-
Signatures of
Try#recover
andTry#recover_with
have changedFear.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') Fear.failure(ArgumentError.new).recover do |m| m.case(ZeroDivisionError) { 0 } m.case(&:message) end #=> Fear.success('ArgumentError')