A pattern matching library for Ruby.
$ gem install pattern-match
or
$ git clone git:https://github.com/k-tsj/pattern-match.git $ cd pattern-match $ gem build pattern-match.gemspec $ gem install pattern-match-*.gem
or
$ gem install bundler (if you need) $ echo "gem 'pattern-match', :git => 'git:https://github.com/k-tsj/pattern-match.git'" > Gemfile $ bundle install --path vendor/bundle
pattern-match library provides Object#match
.
require 'pattern-match' using PatternMatch match(object) do with(pattern[, guard]) do ... end with(pattern[, guard]) do ... end ... end
The patterns are run in sequence until the first one that matches.
If a pattern matches, a block passed to with
is called and return its result. If no pattern matches, a PatternMatch::NoMatchingPatternError exception is raised.
You can specify pattern guard if you want.
An ordinary object is a value pattern.
The pattern matches an object such that pattern === object
.
match(0) do with(Integer) { :match } #=> :match end
If you want to use an another method of matching, you have to use _
as follows.
match(0) do with(_(Integer, :==)) { :match } end #=> NoMatchingPatternError
A deconstructor pattern is (typically) of the form deconstructor.([pattern, ...])
.
It is equivalent to Extractor in Scala.
Consider the following example:
match([0, 1]) do with(Array.(0, 1)) { :match } #=> :match end match('ab') do with(/(.)(.)/.('a', 'b')) { :match } #=> :match end
Array class(Array
), Regexp object(/(.)(.)/
) are deconstructors. You can use any object has the following features as deconstructor.
-
PatternMatch::Deconstructable is included in a class of deconstructor
-
Can be responded to
deconstruct
method
Note that _[]
is provided as syntactic sugar for Array.()
.
match([0, 1]) do with(_[0, 1]) { :match } #=> :match end
An identifier is a variable pattern.
It matches any value, and binds the variable name to that value. A special case is the wild-card pattern _
which matches any value, and never binds.
match([0, 1]) do with(_[a, b]) { [a, b] } #=> [0, 1] end match(0) do with(_) { _ } #=> NameError end
When several patterns with the same name occur in a single pattern, all objects bound to variable must be equal.
match([0, 1]) do with(_[a, a]) { a } end #=> NoMatchingPatternError
PatternMatch::Pattern#&
, PatternMatch::Pattern#|
, PatternMatch::Pattern#!@
, And
, Or
, Not
return and/or/not pattern.
match([0, [1]]) do with(_[a & Integer, ! (_[2] | _[3])]) { a } #=> 0 end match(0) do with(0 | 1 | 2) { } # (0 | 1 | 2) is evaluated to 3, so the pattern does not match. with(Or(0, 1, 2)) { :match } #=> :match end
___
, ___?
, __n
(where n >= 0), __n?
are quantifier patterns.
They are equivalent to *
, *?
, {n,}
, {n,}?
in regular expression. You can write as *pattern
instead of pattern, ___
.
match([:a, 0, :b, :c]) do with(_[a & Symbol, ___, b & Integer, c & Symbol, ___]) do a #=> [:a] b #=> 0 c #=> [:b, :c] end end
Seq
returns a sequence pattern.
It is equivalent to ()
in regular expression.
match([:a, 0, :b, 1]) do with(_[Seq(a & Symbol, b & Integer), ___]) do a #=> [:a, :b] b #=> [0, 1] end end
-
Object.()
-
Matcher
-
KeyMatcher
-
Hash.()
-
-
AttributeMatcher
-
-
fallthrough (requires binding_of_caller gem)
To use experimental features, you must also require ‘pattern-match/experimental’. See source code for more details.
Pattern guard can be specified as a second argument to with
.
match([1, 2, 3, 4, 5]) do with(_[*_, *a, *_], guard { a.inject(:*) == 12 }) do a #=> [3, 4] end end
# (A) Node = Struct.new(:left, :key, :right) class R < Node; end class B < Node; end def balance(left, key, right) match([left, key, right]) do with(_[R.(a, x, b), y, R.(c, z, d)]) { R[B[a, x, b], y, B[c, z, d]] } with(_[R.(R.(a, x, b), y, c), z, d]) { R[B[a, x, b], y, B[c, z, d]] } with(_[R.(a, x, R.(b, y, c)), z, d]) { R[B[a, x, b], y, B[c, z, d]] } with(_[a, x, R.(b, y, R.(c, z, d))]) { R[B[a, x, b], y, B[c, z, d]] } with(_[a, x, R.(R.(b, y, c), z, d)]) { R[B[a, x, b], y, B[c, z, d]] } with(_) { B[left, key, right] } end end # (B) class EMail def self.deconstruct(value) parts = value.to_s.split(/@/) if parts.length == 2 parts else raise PatternMatch::PatternNotMatch end end end match(['[email protected]', '[email protected]']) do with(_[mail & EMail.(name & /(\w+)-(\w+)/.(firstname, 'bar'), domain), ___]) do mail #=> ["[email protected]", "[email protected]"] name #=> ["foo-bar", "baz-bar"] firstname #=> ["foo", "baz"] domain #=> ["example.com", "example.com"] end end # (C) def replace_repeated(obj, &block) ret = match(obj, &block) if ret == obj ret else replace_repeated(ret, &block) end rescue PatternMatch::NoMatchingPatternError obj end replace_repeated([1, 2, 4, 4, 3, 3, 4, 0, 0]) do with(_[*a, x, x, *b]) { [*a, x, *b] } end #=> [1, 2, 4, 3, 4, 0] # (D) require 'pattern-match/experimental' match({a: 0, b: 1}) do with(Hash.(:a, b: Object.(odd?: true))) do a #=> 0 end end C = Struct.new(:a, :b) do include PatternMatch::AttributeMatcher end match(C[0, 1]) do with(C.(:b, a: 0)) do b # => 1 end end match('0') do with(/\d+/.(a << :to_i)) do a #=> 0 end end match([Set[0, 1, 2], Set[3, 4]]) do with(_[Set.(a, b), Set.(c)], guard { a + b * c == 2 } ) do [a, b, c] #=> [2, 0, 3] end end match([]) do with(_[]) do opts = {} fallthrough end with(_[opts & Hash]) do opts #=> {} end end
$ git clone git:https://github.com/k-tsj/pattern-match.git $ cd pattern-match $ gem install bundler (if you need) $ bundle install --path vendor/bundle $ bundle exec rake test (or "bundle exec rake") $ bundle exec rake build