Class: Qo::Branches::Branch

Inherits:
Object
  • Object
show all
Defined in:
lib/qo/branches/branch.rb

Overview

Branches

A branch is a particular branch of a pattern match. The default branches emulate a case statement. Consider a case statement like this:

case value
when condition then first_return
else second_return
end

With a Qo branch you would see something like this:

Qo.match { |m|
  m.when(condition) { first_return }
  m.else { second_return }
}

The when and else are the names the branch was "registered" with in Qo::PatternMatchers::Branching. The name becomes the method name that the associated matcher uses.

Order of Execution

A branch will execute in the following order:

value -> precondition ? -> extractor -> condition ? -> destructurer

Preconditions allow for things like type checks or any static condition that will remain constant across all matches. Think of them as abstracting a single condition to guard before the branch continues.

Conditions are typical Qo matchers, as documented in the README. Upon a match, the branch will be considered matched and continue on to calling the associated block function.

Extractors are used to pull a value out of a container type, such as value for monadic types or last for response array tuples.

Lastly, if given, Destructurers will destructure an object. That means that the associated function now places great significance on the names of the arguments as they'll be used to extract values from the object that would have normally been returned.

Destructuring can be a complicated topic, see the following article to find out more on how this works or see the README for examples:

https://medium.com/rubyinside/destructuring-in-ruby-9e9bd2be0360

Match Tuples

Branches will respond with a tuple of (status, value). A status of false indicates a non-match, and a status or true indicates a match. This is done to ensure that truly false or nil returns are not swallowed by a match.

A Pattern Match will use these statuses to find the first matching branch.

Author:

  • baweaver

Since:

  • 1.0.0

Constant Summary

UNMATCHED =

Representation of an unmatched value. These values are wrapped in array tuples to preserve legitimate false and nil values by indicating the status of the match in the first position and the returned value in the second.

Since:

  • 1.0.0

[false, nil]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, precondition: Any, extractor: IDENTITY, destructure: false, default: false) ⇒ Qo::Branches::Branch

Creates an instance of a Branch

Parameters:

  • name: (String)

    Name of the branch. This is what binds to the pattern match as a method, meaning a name of where will result in calling it as m.where.

  • precondition: (defaults to: Any)

    Any [Symbol, #===] A precondition to the branch being considered true. This is done for static conditions like a certain type or perhaps checking a tuple type like [:ok, value].

    If a Symbol is given, Qo will coerce it into a proc. This is done to make a nicer shorthand for creating a branch.

  • extractor: (defaults to: IDENTITY)

    IDENTITY [Proc, Symbol] How to pull the value out of a target object when a branch matches before calling the associated function. For a monadic type this might be something like extracting the value before yielding to the given block.

    If a Symbol is given, Qo will coerce it into a proc. This is done to make a nicer shorthand for creating a branch.

  • destructure: (defaults to: false)

    false Whether or not to destructure the given object before yielding to the associated block. This means that the given block now places great importance on the argument names, as they'll be used to extract values from the associated object by that same method name, or key name in the case of hashes.

  • default: (defaults to: false)

    false [Boolean] Whether this branch is considered to be a default condition. This is done to ensure that a branch runs last after all other conditions have failed. An example of this would be an else branch.

Since:

  • 1.0.0



113
114
115
116
117
118
119
# File 'lib/qo/branches/branch.rb', line 113

def initialize(name:, precondition: Any, extractor: IDENTITY, destructure: false, default: false)
  @name         = name
  @precondition = precondition.is_a?(Symbol) ? precondition.to_proc : precondition
  @extractor    = extractor.is_a?(Symbol)    ? extractor.to_proc    : extractor
  @destructure  = destructure
  @default      = default
end

Instance Attribute Details

#nameObject (readonly)

Name of the branch, see the initializer for more information

Since:

  • 1.0.0



76
77
78
# File 'lib/qo/branches/branch.rb', line 76

def name
  @name
end

Class Method Details

.create(name:, precondition: Any, extractor: IDENTITY, destructure: false, default: false) ⇒ Class

A dynamic creator for new branch types to be made on the fly in programs. This exists to make new types of pattern matches to suit your own needs.

Prefer the public API to using this method directly, Qo.create_branch, mostly because it's less typing.

Returns:

  • (Class)

    new Class to be bound to a constant name, or used anonymously

See Also:

  • for parameter documentation

Since:

  • 1.0.0



131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/qo/branches/branch.rb', line 131

def self.create(name:, precondition: Any, extractor: IDENTITY, destructure: false, default: false)
  attributes = {
    name:         name,
    precondition: precondition,
    extractor:    extractor,
    destructure:  destructure,
    default:      default
  }

  Class.new(Qo::Branches::Branch) do
    define_method(:initialize) { super(**attributes) }
  end
end

Instance Method Details

#create_matcher(conditions, destructure: @destructure, &function) ⇒ Proc[Any]

Uses the current configuration of the branch to create a matcher to be used in a pattern match. The returned proc can be passed a value that will return back a tuple of (status, value) to indicate whether or not a match was made with this branch.

Parameters:

  • conditions (#===)

    A set of conditions to run against, typically a Qo.and matcher but could be anything that happens to respond to ===.

  • destructure: (defaults to: @destructure)

    false [Boolean] Whether or not to run the extracted value through a destructure before yielding it to the associated block.

  • &function (Proc)

    Function to be called if a matcher matches.

Returns:

  • (Proc[Any])

    [description]

Since:

  • 1.0.0



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/qo/branches/branch.rb', line 169

def create_matcher(conditions, destructure: @destructure, &function)
  function ||= IDENTITY

  destructurer = Destructurers::Destructurer.new(
    destructure: destructure, &function
  )

  Proc.new { |value|
    # If it's a default branch, return true, as conditions are redundant
    if @default
      extracted_value = @extractor.call(value)
      next [true, destructurer.call(extracted_value)]
    end

    # Otherwise we check the precondition first before extracting the
    # value from whatever container it might be in.
    next UNMATCHED unless @precondition === value
    
    extracted_value = @extractor.call(value)
    
    # If that extracted value matches our conditions, destructure the value
    # and return it, or return unmatched otherwise.
    conditions === extracted_value ?
      [true, destructurer.call(extracted_value)] :
      UNMATCHED
  }
end

#default?Boolean

Whether or not this is a default branch

Returns:

  • (Boolean)

Since:

  • 1.0.0



148
149
150
# File 'lib/qo/branches/branch.rb', line 148

def default?
  @default
end