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 Kernel#match.
require 'pattern-match'
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 object (expect the instance of PatternMatch::Pattern) is a value pattern.
The pattern matches an object such that pattern === object
.
match(0) do
with(Fixnum) { :match } #=> :match
end
If you want to use an another method of matching, you have to use _
as follows.
match(0) do
with(_(Fixnum, :==)) { :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, 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
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 & Finuxm, ! (_[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
_
(triple underscore), _?
, __n
(double underscore + 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 & Fixnum, 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 & Fixnum), ___]) do
a #=> [:a, :b]
b #=> [0, 1]
end
end
EXPERIMENTAL
-
Object.()
-
Matcher
-
KeyMatcher
-
Hash.()
-
-
AttributeMatcher
-
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)
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
Reference
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