-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add exhaustive match #26
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,6 +8,10 @@ module PatternMatchers | |||||
class PatternMatch | ||||||
include Branching | ||||||
|
||||||
# All matchers that have currently been added to an instance | ||||||
# of a pattern match | ||||||
attr_reader :provided_matchers | ||||||
|
||||||
# The regular pattern matcher from classic Qo uses `when` and `else` | ||||||
# branches, like a `case` statement | ||||||
register_branch Qo::Branches::WhenBranch.new | ||||||
|
@@ -18,16 +22,28 @@ class PatternMatch | |||||
# @param destructure: false [Boolean] | ||||||
# Whether or not to destructure values before yielding to a block | ||||||
# | ||||||
# @param exhaustive: false [Boolean] | ||||||
# If no matches are found, this will raise a | ||||||
# `Qo::Errors::ExhaustiveMatchNotMet` error. | ||||||
# | ||||||
# @param &fn [Proc] | ||||||
# Function to be used to construct the pattern matcher's branches | ||||||
# | ||||||
# @return [Qo::PatternMatchers::PatternMatch] | ||||||
def initialize(destructure: false, &fn) | ||||||
def initialize(destructure: false, exhaustive: false, &fn) | ||||||
@matchers = [] | ||||||
@default = nil | ||||||
@destructure = destructure | ||||||
@exhaustive = exhaustive | ||||||
|
||||||
yield(self) if block_given? | ||||||
|
||||||
if lacking_branches? | ||||||
raise Qo::Exceptions::ExhaustiveMatchMissingBranches.new( | ||||||
expected_branches: available_branch_names, | ||||||
given_branches: provided_matchers | ||||||
) | ||||||
end | ||||||
end | ||||||
|
||||||
# Allows for the creation of an anonymous PatternMatcher based on this | ||||||
|
@@ -116,13 +132,19 @@ def self.create(branches: []) | |||||
# @param destructure: false [Boolean] | ||||||
# Whether or not to destructure values before yielding to a block | ||||||
# | ||||||
# @param exhaustive: false [Boolean] | ||||||
# If no matches are found, this will raise a | ||||||
# `Qo::Errors::ExhaustiveMatchNotMet` error. | ||||||
# | ||||||
# @param as: :match [Symbol] | ||||||
# Name to use as a method name bound to the including class | ||||||
# | ||||||
# @return [Module] | ||||||
# Module to be mixed into a class | ||||||
def self.mixin(destructure: false, as: :match) | ||||||
create_self = -> &function { new(destructure: destructure, &function) } | ||||||
def self.mixin(destructure: false, exhaustive: false, as: :match) | ||||||
create_self = -> &function { | ||||||
new(destructure: destructure, exhaustive: exhaustive, &function) | ||||||
} | ||||||
|
||||||
Module.new do | ||||||
define_method(as) do |&function| | ||||||
|
@@ -131,15 +153,59 @@ def self.mixin(destructure: false, as: :match) | |||||
end | ||||||
end | ||||||
|
||||||
# Whether or not the current pattern match requires a matching branch | ||||||
# | ||||||
# @return [Boolean] | ||||||
def exhaustive? | ||||||
@exhaustive | ||||||
end | ||||||
|
||||||
# Whether or not the current pattern match is exhaustive and has a missing | ||||||
# default branch | ||||||
# | ||||||
# @return [Boolean] | ||||||
def exhaustive_no_default? | ||||||
exhaustive? && !@default | ||||||
end | ||||||
|
||||||
# Names of all of the available branch names set in `Branching` on | ||||||
# registration of a branch | ||||||
# | ||||||
# @return [Array[String]] | ||||||
def available_branch_names | ||||||
self.class.available_branches.keys | ||||||
end | ||||||
|
||||||
# Whether or not all branch types have been provided to the matcher. | ||||||
# | ||||||
# @return [Boolean] | ||||||
def all_branches_provided? | ||||||
available_branch_names == @provided_matchers.uniq | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May fail if you provide branches in order other than order of registration
Suggested change
or you can store names as an instance of Set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to integrate latest changes into my PR, and got this error: Qo::Exceptions::ExhaustiveMatchMissingBranches:
Exhaustive match required: pattern does not specify all branches.
Expected Branches: right, left
Given Branches: left, right There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I may jump back to implementing this with sets, silly mistake on my part. |
||||||
end | ||||||
|
||||||
# Whether the current matcher is lacking branches | ||||||
# | ||||||
# @return [Boolean] | ||||||
def lacking_branches? | ||||||
exhaustive_no_default? && !all_branches_provided? | ||||||
end | ||||||
|
||||||
# Calls the pattern matcher, yielding the target value to the first | ||||||
# matching branch it encounters. | ||||||
# | ||||||
# In the case of an exhaustive match, this will raise an error if no | ||||||
# default branch is provided. | ||||||
# | ||||||
# @param value [Any] | ||||||
# Value to match against | ||||||
# | ||||||
# @return [Any] | ||||||
# Result of the called branch | ||||||
# | ||||||
# @raises [Qo::Exceptions::ExhaustiveMatchNotMet] | ||||||
# If the matcher is exhaustive and no default branch is provided, it is | ||||||
# considered to have failed an optimistic exhaustive match. | ||||||
# | ||||||
# @return [nil] | ||||||
# Returns nil if no branch is matched | ||||||
def call(value) | ||||||
|
@@ -148,6 +214,8 @@ def call(value) | |||||
return return_value if status | ||||||
end | ||||||
|
||||||
raise Qo::Exceptions::ExhaustiveMatchNotMet if exhaustive_no_default? | ||||||
|
||||||
if @default | ||||||
_, return_value = @default.call(value) | ||||||
return_value | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
module Qo | ||
VERSION = '0.99.0' | ||
VERSION = '0.99.1' | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest having common ancestor for all Qo errors and, probably for exhaustive match errors. So end users will be able to catch errors with desired granularity. For example Qo::Error, Qo, MatchError, ExhaustiveMatchNotMet etc.