pattern-match
pattern-match copied to clipboard
A pattern matching library for Ruby
= pattern-match == About A pattern matching library for Ruby.
== Installation $ 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
== Basic Usage
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.
== Patterns === Value 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
=== Deconstructor
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
deconstructmethod
Note that _[] is provided as syntactic sugar for Array.().
match([0, 1]) do with(_[0, 1]) { :match } #=> :match end
=== Variable 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
=== And/Or/Not
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
=== Quantifier
_, _?,
__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
=== Sequence
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
=== EXPERIMENTAL
- Object.()
- Matcher
- KeyMatcher
- Hash.()
- AttributeMatcher
- KeyMatcher
- 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
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
== Examples
(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
- {tokland/RubyTextProcessing}[https://github.com/tokland/tokland/wiki/RubyTextProcessing]
- {yhara/tapl-ruby}[https://github.com/yhara/tapl-ruby]
- {k-tsj/power_assert}[https://github.com/k-tsj/power_assert/blob/8e9e0399a032936e3e3f3c1f06e0d038565f8044/lib/power_assert.rb]
== Reference
- {Pattern Matching in Ruby (at Sapporo RubyKaigi 2012) // Speaker Deck}[https://speakerdeck.com/k_tsj/patternmatchinginruby]
== Development $ 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