Class: Dry::Matcher

Inherits:
Object
  • Object
show all
Includes:
Core::Constants
Defined in:
lib/dry/matcher.rb,
lib/dry/matcher/case.rb,
lib/dry/matcher/match.rb,
lib/dry/matcher/version.rb,
lib/dry/matcher/evaluator.rb,
lib/dry/matcher/maybe_matcher.rb,
lib/dry/matcher/either_matcher.rb,
lib/dry/matcher/result_matcher.rb

Overview

Defined Under Namespace

Classes: Case, Evaluator

Constant Summary collapse

RUBY2_KEYWORDS =
respond_to?(:ruby2_keywords, true)
PatternMatch =
proc do |value, patterns|
  if patterns.empty?
    value
    # rubocop:disable Lint/DuplicateBranch
  elsif value.is_a?(::Array) && patterns.any? { |p| p === value[0] }
    value
  elsif patterns.any? { |p| p === value }
    # rubocop:enable Lint/DuplicateBranch
    value
  else
    Undefined
  end
end
VERSION =
'1.0.0'
NonExhaustiveMatchError =
Class.new(StandardError)
MaybeMatcher =

Built-in Dry::Matcher ready to use with ‘Maybe` monad from [dry-monads](/gems/dry-monads) or any other compatible gems.

Provides Cases for two matchers:

  • ‘:some` matches `Dry::Monads::Maybe::Some`

  • ‘:none` matches `Dry::Monads::Maybe::None`

Examples:

Usage with ‘dry-monads`

require 'dry/monads'
require 'dry/matcher/maybe_matcher'

value = Dry::Monads::Maybe.new('there is a value!')

Dry::Matcher::MaybeMatcher.(value) do |m|
  m.some do |v|
    "Yay: #{v}"
  end

  m.none do
    "Boo: none"
  end
end #=> "Yay: there is a value!"

Usage with specific types

value = Dry::Monads::Maybe.new([200, :ok])

Dry::Matcher::MaybeMatcher.(value) do |m|
  m.some(200, :ok) do |code, value|
    "Yay: #{value}"
  end

  m.none do
    "Boo: none"
  end
end #=> "Yay: :ok"

Returns:

Dry::Matcher.new(
  some: Case.new { |maybe, patterns|
    if maybe.none?
      Undefined
    else
      Dry::Matcher::PatternMatch.(maybe.value!, patterns)
    end
  },
  none: Case.new { |maybe|
    if maybe.some?
      Undefined
    end
  }
)
EitherMatcher =
ResultMatcher
ResultMatcher =

Built-in Dry::Matcher ready to use with ‘Result` or `Try` monads from [dry-monads](/gems/dry-monads) or any other compatible gems.

Provides Cases for two matchers:

  • ‘:success` matches `Dry::Monads::Result::Success` and `Dry::Monads::Try::Value` (or any other monad that responds to `#to_result` returning result monad that is `#success?`)

  • ‘:failure` matches `Dry::Monads::Result::Failure` and `Dry::Monads::Try::Error` (or any other monad that responds to `#to_result` returning result monad that is `#failure?`)

Examples:

Usage with ‘dry-monads`

require 'dry/monads'
require 'dry/matcher/result_matcher'

value = Dry::Monads::Result::Success.new('success!')

Dry::Matcher::ResultMatcher.(value) do |m|
  m.success do |v|
    "Yay: #{v}"
  end

  m.failure do |v|
    "Boo: #{v}"
  end
end #=> "Yay: success!"

Usage with custom monad

require 'dry/matcher/result_matcher'

class CustomBooleanMonad
  def initialize(value); @value = value; end
  attr_reader :value
  alias_method :success?, :value
  def failure?; !success?; end
  def to_result; self; end
end

value = CustomBooleanMonad.new(nil)

Dry::Matcher::ResultMatcher.(value) do |m|
  m.success { |v| "#{v.inspect} is truthy" }
  m.failure { |v| "#{v.inspect} is falsey" }
end # => "nil is falsey"

Usage with error codes

value = Dry::Monads::Result::Failure.new([:invalid, :reasons])

Dry::Matcher::ResultMatcher.(value) do |m|
  m.success do |v|
    "Yay: #{v}"
  end

  m.failure(:not_found) do
    "No such thing"
  end

  m.failure(:invalid) do |_code, errors|
    "Cannot be done: #{errors.inspect}"
  end
end #=> "Cannot be done: :reasons"

Returns:

Dry::Matcher.new(
  success: Case.new { |result, patterns|
    result = result.to_result

    if result.success?
      Dry::Matcher::PatternMatch.(result.value!, patterns)
    else
      Undefined
    end
  },
  failure: Case.new { |result, patterns|
    result = result.to_result

    if result.failure?
      Dry::Matcher::PatternMatch.(result.failure, patterns)
    else
      Undefined
    end
  }
)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cases = {}) ⇒ Matcher

Returns a new instance of Matcher.

Parameters:

  • cases (Hash{Symbol => Case}) (defaults to: {})


67
68
69
# File 'lib/dry/matcher.rb', line 67

def initialize(cases = {})
  @cases = cases
end

Instance Attribute Details

#casesHash{Symbol => Case} (readonly)

Returns:

  • (Hash{Symbol => Case})


64
65
66
# File 'lib/dry/matcher.rb', line 64

def cases
  @cases
end

Class Method Details

.for(*match_methods, with:) ⇒ Module

Generates a module containing pattern matching for methods listed in ‘match_methods` argument with behavior defined by `with` matcher

Examples:

Usage with ‘dry-monads`

class MonadicOperation
  include Dry::Matcher.for(:call, with: Dry::Matcher::ResultMatcher)

  def call
    Dry::Monads::Result::Success.new('Success')
  end
end

operation = MonadicOperation.new

operation.call do |m|
  m.success { |v| "#{v} was successful!"}
  m.failure { |v| "#{v} has failed!"}
end #=> "Success was successful"

Parameters:

Returns:

  • (Module)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/dry/matcher.rb', line 36

def self.for(*match_methods, with:)
  matcher = with

  matchers_mod = Module.new do
    match_methods.each do |match_method|
      define_method(match_method) do |*args, &block|
        result = super(*args)

        if block
          matcher.(result, &block)
        else
          result
        end
      end
      ruby2_keywords(match_method) if RUBY2_KEYWORDS
    end
  end

  Module.new do
    const_set :Matchers, matchers_mod

    def self.included(klass)
      klass.prepend const_get(:Matchers)
    end
  end
end

Instance Method Details

#call(result, &block) {|m| ... } ⇒ Object

Evaluate #cases‘ matchers and returns a result of block given to corresponding case matcher

Examples:

Usage with ‘dry-monads`

require 'dry/monads'
require 'dry/matcher/result_matcher'

value = Dry::Monads::Result::Failure.new('failure!')

Dry::Matcher::ResultMatcher.(value) do |m|
  m.success { |v| "Yay: #{v}" }
  m.failure { |v| "Boo: #{v}" }
end #=> "Boo: failure!"

Parameters:

  • result (Object)

    value that would be tested for matches with given #cases

  • block (Object)

Yield Parameters:

Returns:

  • (Object)

    value returned from the block given to method called after matched pattern



90
91
92
# File 'lib/dry/matcher.rb', line 90

def call(result, &block)
  Evaluator.new(result, cases).call(&block)
end

#for(*methods) ⇒ Module

Shortcut for Dry::Matcher.for(…, with: matcher)

Parameters:

  • (Array[Symbol])

Returns:

  • (Module)


99
100
101
# File 'lib/dry/matcher.rb', line 99

def for(*methods)
  self.class.for(*methods, with: self)
end