Skip to content

Repository files navigation

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 deconstruct method

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

About

A pattern matching library for Ruby

Resources

Stars

254 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages