A pattern matching library for Ruby.
$ gem install pattern-match
or
$ git clone git://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://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) dowith(Integer) { :match } #=> :matchend
If you want to use an another method of matching, you have to use _ as follows.
match(0) dowith(_(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]) dowith(Array.(0, 1)) { :match } #=> :matchendmatch('ab') dowith(/(.)(.)/.('a', 'b')) { :match } #=> :matchend
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
deconstructmethod
Note that _[] is provided as syntactic sugar for Array.().
match([0, 1]) dowith(_[0, 1]) { :match } #=> :matchend
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]) dowith(_[a, b]) { [a, b] } #=> [0, 1]endmatch(0) dowith(_) { _ } #=> NameErrorend
When several patterns with the same name occur in a single pattern, all objects bound to variable must be equal.
match([0, 1]) dowith(_[a, a]) { a } end#=> NoMatchingPatternError
PatternMatch::Pattern#&, PatternMatch::Pattern#|, PatternMatch::Pattern#!@, And, Or, Not return and/or/not pattern.
match([0, [1]]) dowith(_[a&Integer, ! (_[2] |_[3])]) { a } #=> 0endmatch(0) dowith(0|1|2) { } # (0 | 1 | 2) is evaluated to 3, so the pattern does not match.with(Or(0, 1, 2)) { :match } #=> :matchend
___, ___?, __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]) dowith(_[a&Symbol, ___, b&Integer, c&Symbol, ___]) doa#=> [:a]b#=> 0c#=> [:b, :c]endend
Seq returns a sequence pattern.
It is equivalent to () in regular expression.
match([:a, 0, :b, 1]) dowith(_[Seq(a&Symbol, b&Integer), ___]) doa#=> [:a, :b]b#=> [0, 1]endend
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]) dowith(_[*_, *a, *_], guard { a.inject(:*) ==12 }) doa#=> [3, 4]endend
# (A)Node = Struct.new(:left, :key, :right) classR<Node; endclassB<Node; enddefbalance(left, key, right) match([left, key, right]) dowith(_[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] } endend# (B)classEMaildefself.deconstruct(value) parts = value.to_s.split(/@/) ifparts.length==2partselseraisePatternMatch::PatternNotMatchendendendmatch(['foo-bar@example.com', 'baz-bar@example.com']) dowith(_[mail&EMail.(name&/(\w+)-(\w+)/.(firstname, 'bar'), domain), ___]) domail#=> ["foo-bar@example.com", "baz-bar@example.com"]name#=> ["foo-bar", "baz-bar"]firstname#=> ["foo", "baz"]domain#=> ["example.com", "example.com"]endend# (C)defreplace_repeated(obj, &block) ret = match(obj, &block) ifret==objretelsereplace_repeated(ret, &block) endrescuePatternMatch::NoMatchingPatternErrorobjendreplace_repeated([1, 2, 4, 4, 3, 3, 4, 0, 0]) dowith(_[*a, x, x, *b]) { [*a, x, *b] } end#=> [1, 2, 4, 3, 4, 0]# (D)require'pattern-match/experimental'match({a:0, b:1}) dowith(Hash.(:a, b:Object.(odd?:true))) doa#=> 0endendC = Struct.new(:a, :b) doincludePatternMatch::AttributeMatcherendmatch(C[0, 1]) dowith(C.(:b, a:0)) dob# => 1endendmatch('0') dowith(/\d+/.(a<<:to_i)) doa#=> 0endendmatch([Set[0, 1, 2], Set[3, 4]]) dowith(_[Set.(a, b), Set.(c)], guard { a+b*c==2 } ) do [a, b, c] #=> [2, 0, 3]endendmatch([]) dowith(_[]) doopts = {} fallthroughendwith(_[opts&Hash]) doopts#=> {}endend
$ git clone git://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