module PatternMatching
Allows for crude pattern-matching like behavior. Very crude. Currently just a Symbol as a status tag paired with a value. Provides capitalized methods (bad form, perhaps?) to make them stand out: `#Pattern`, `#Result` and `#Match`.
Example usage:
def some_computation
Result(:ok, "awesome sauce")
end
case (result = some_computation) when Match(:ok)
puts "Things went okay: #{result.value}"
when Match(:error)
puts "Something went wrong: #{result.value}"
end
# => “Things went okay: awesome sauce”
Originally named `Result` but that would have a conflicting meaning with the typical use of the Result Monad in most languages/environments. So while this is not true pattern matching, it might one day grow into something of the sort, and so the name fits… in a limited way.
Constants
- Any
- Head
- Tail
- Undefined
Additional singleton constants and wildcards
- VERSION
Public Class Methods
The implementation of the METHODS heredoc should remain identical to the contents of the Match and Pattern methods of PatternMatching::MethodsWithBindingHelper
except that here we are interpolating on our
# File lib/pattern_matching.rb, line 138 def self.build_custom_binding_helper(binding_helper) methods_with_binding = ->() { eval( <<-METHODS def Match(*pattern) result = ::PatternMatching::CaseEqualityReversal.new(*pattern) (self.class)::#{binding_helper}._clear_bindings!(caller_locations(1,1)[0].label) unless result result end def Pattern(*pattern) (self.class)::#{binding_helper}._clear_bindings!(caller_locations(1,1)[0].label) ::PatternMatching::PatternMatch.new(*pattern) end METHODS ) } @methods_with_custom_binding_helper = Module.new @methods_with_custom_binding_helper.class_exec(&methods_with_binding) end
Implementations internal to the blocks of the define_method calls should remain identical to the implementations in the PatternMatching::ProcHelpers
module, with the only change being that the produced module has different names for the methods behind the call and send helpers.
# File lib/pattern_matching.rb, line 119 def self.build_custom_proc_helpers(send_helper, call_helper) proc_helpers = ->() { define_method(send_helper) do |symbol| symbol.to_proc end define_method(call_helper) do |symbol| Proc.new { |obj| self.send(symbol, obj) } end } @custom_proc_helpers = Module.new @custom_proc_helpers.class_exec(&proc_helpers) end
Simple class-instance variable to hold configuration
# File lib/pattern_matching.rb, line 84 def self.config @config ||= ::PatternMatching::Configuration.default end
Available configuration options are:
-
use_proc_helpers: controls whether or not helpers for sending messages and calling a method in the local context are included with this module.
-
use_binding_helper: controls whether or not bindings are enabled (and thus) whether or not helpers are included.
-
send_helper: the method name used as the proc helper for “sending a message” to the object when matching.
-
call_helper: the method name used as the proc helper for “calling a method in the current context” with the object as an argument when matching.
-
binding_helper: the method name used as the binding set for each match.
# File lib/pattern_matching.rb, line 69 def self.configure(&block) block.call(config) unless config.default_proc_helpers? build_custom_proc_helpers(config.send_helper, config.call_helper) end if config.use_binding_helper && !config.default_binding_helper? build_custom_binding_helper(config.binding_helper) puts "Using a custom binding helper are we?" end end
Will only be set with a proper module if a call to ::configure
results in there being proc helpers with non-default names. Is not intelligently assigned, nor should it be used, when called in any manner before a call to ::configure
that specifies an alternative value to config.call_helper and config.binding_helper.
# File lib/pattern_matching.rb, line 101 def self.custom_proc_helpers @custom_proc_helpers end
For of use in testing, because of how Ruby loading works when testing behavior that is thread-global (?) for the instance of this module.
# File lib/pattern_matching.rb, line 91 def self.default_configuration! @config = ::PatternMatching::Configuration.default end
Configure behavior based on existing configuration. This is a lot of noodley-looking nested conditional logic but that's kind of the point with lots of boolean-based configuration of behavior!
# File lib/pattern_matching.rb, line 31 def self.included(base) if PatternMatching.config.use_binding_helper base.const_set(PatternMatching.config.binding_helper, PatternMatching::BindingsSet.new) if PatternMatching.config.default_binding_helper? base.send(:include, PatternMatching::MethodsWithBindingHelper) else base.send(:include, PatternMatching.methods_with_custom_binding_helper) end else base.send(:include, PatternMatching::Methods) end if PatternMatching.config.use_proc_helpers if PatternMatching.config.default_proc_helpers? base.send(:include, PatternMatching::ProcHelpers) else base.send(:include, PatternMatching.custom_proc_helpers) end end end
Will only be set with a proper module if a call to ::configure
results in a binding helper set to a name which is not the default. Is not intelligently assigned, nor should it be used, when called in any manner before a call to ::configure
that specifies an alternative value to config.binding_helper
# File lib/pattern_matching.rb, line 110 def self.methods_with_custom_binding_helper @methods_with_custom_binding_helper end
Public Instance Methods
Handles matching for non-collection values, including the logic behind the wildcard Any
. In the case of a collection, defers instead to match_enumerable.
# File lib/pattern_matching/pattern_match.rb, line 44 def match_item(from_self, from_other) if Any == from_other true elsif Enumerable === from_other && Enumerable === from_self match_enumerable(from_self, from_other) else from_other === from_self end end