This Gem provides a way to access non-linear pattern-matching against unfree data types from Ruby. We can directly express pattern-matching against lists, multisets, and sets using this gem.
$ gem install egisonor
$ git clone https://github.com/egison/egison-ruby.git
$ cd egison-ruby
$ makeor
$ gem install bundler (if you need)
$ echo "gem 'egison', :git => 'https://github.com/egison/egison-ruby.git'" > Gemfile
$ bundle installThe library provides Egison#match_all and Egison#match.
require 'egison'
include Egison
match_all(object) do
with(pattern) do
...
end
end
match(object) do
with(pattern) do
...
end
with(pattern) do
...
end
...
endIf a pattern matches, a block passed to with is called and returns its result.
In our pattern-matching system, there are cases that pattern-matching has multiple results.
match_all calls the block passed to with for each pattern-matching result and returns all results as an array.
match_all takes one single match-clause.
On the other hand, match takes multiple match-clauses.
It pattern-matches from the first match-clause.
If a pattern matches, it calls the block passed to the matched match-clause and returns a result for the first pattern-matching result.
An element pattern matches the element of the target array.
A subcollection pattern matches the subcollection of the target array.
A subcollection pattern has * ahead.
A literal that contain _ ahead is a pattern-variable.
We can refer the result of pattern-matching through them.
match_all([1, 2, 3]) do
with(List.(*_hs, _x, *_ts)) do
[hs, x, ts]
end
end #=> [[[],1,[2,3]],[[1],2,[3]],[[1,2],3,[]]We can write pattern-matching against lists, multisets, and sets. When we regard an array as a multiset, the order of elements is ignored. When we regard an array as a set, the duplicates and order of elements are ignored.
_ is a wildcard.
It matches with any object.
Note that __ and ___ are also interpreted as a wildcard.
This is because _ and __ are system variables and sometimes have its own meaning.
match_all([1, 2, 3]) do
with(List.(_a, _b, *_)) do
[a, b]
end
end #=> [[1, 2]]
match_all([1, 2, 3]) do
with(Multiset.(_a, _b, *_)) do
[a, b]
end
end #=> [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
match_all([1, 2, 3]) do
with(Set.(_a, _b, *_)) do
[a, b]
end
end #=> [[1, 1],[1, 2],[1, 3],[2, 1],[2, 2],[2, 3],[3, 1],[3, 2],[3, 3]]Note that _[] is provided as syntactic sugar for List.().
match_all([1, 2, 3]) do
with(_[_a, _b, *_]) do
[a, b]
end
end #=> [[1, 2]]Non-linear pattern is the most important feature of our pattern-matching system.
Our pattern-matching system allows users multiple occurrences of same variables in a pattern.
A Pattern whose form is __("...") is a value pattern.
In the place of ..., we can write any ruby expression we like.
It matches the target when the target is equal with the value that ... evaluated to.
match_all([5, 3, 4, 1, 2]) do
with(Multiset.(_a, __("a + 1"), __("a + 2"), *_)) do
a
end
end #=> [1,2,3]When, the expression in the place of ... is a single variable, we can omit (" and ") as follow.
match_all([1, 2, 3, 2, 5]) do
with(Multiset.(_a, __a, *_)) do
a
end
end #=> [2,2]We can do pattern-matching against streams with the match_stream expression.
def nats
(1..Float::INFINITY)
end
match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)
#=>[[1, 2], [1, 3], [2, 1], [1, 4], [2, 3], [3, 1], [1, 5], [2, 4], [3, 2], [4, 1]]
match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)
#=>[[1, 1], [1, 2], [2, 1], [1, 3], [2, 2], [3, 1], [1, 4], [2, 3], [3, 2], [4, 1]]We can enumerates all combinations of the elements of a collection with pattern-matching.
require 'egison'
include Egison
p(match_all([1,2,3,4,5]) do with(List.(*_, _x, *_, _y, *_)) { [x, y] } end)
#=> [[1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]]
p(match_all([1,2,3,4,5]) do with(List.(*_, _x, *_, _y, *_, _z, *_)) { [x, y, z] } end)
#=> [[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]]We can write patterns for all poker-hands in one single pattern. It is as follow. Isn't it exciting?
require 'egison'
include Egison
def poker_hands cs
match(cs) do
with(Multiset.(_[_s, _n], _[__s, __("n+1")], _[__s, __("n+2")], _[__s, __("n+3")], _[__s, __("n+4")])) do
"Straight flush"
end
with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _[_, __n], _)) do
"Four of kind"
end
with(Multiset.(_[_, _m], _[_, __m], _[_, __m], _[_, _n], _[_, __n])) do
"Full house"
end
with(Multiset.(_[_s, _], _[__s, _], _[__s, _], _[__s, _], _[__s, _])) do
"Flush"
end
with(Multiset.(_[_, _n], _[_, __("n+1")], _[_, __("n+2")], _[_, __("n+3")], _[_, __("n+4")])) do
"Straight"
end
with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _, _)) do
"Three of kind"
end
with(Multiset.(_[_, _m], _[_, __m], _[_, _n], _[_, __n], _)) do
"Two pairs"
end
with(Multiset.(_[_, _n], _[_, __n], _, _, _)) do
"One pair"
end
with(Multiset.(_, _, _, _, _)) do
"Nothing"
end
end
end
p(poker_hands([["diamond", 1], ["diamond", 3], ["diamond", 5], ["diamond", 4], ["diamond", 2]])) #=> "Straight flush"
p(poker_hands([["diamond", 1], ["club", 2], ["club", 1], ["heart", 1], ["diamond", 2]])) #=> "Full house"
p(poker_hands([["diamond", 4], ["club", 2], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Straight"
p(poker_hands([["diamond", 4], ["club", 10], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Nothing"The following code enumerates all twin primes with pattern-matching! I believe it is also a really exciting demonstration.
require 'egison'
require 'prime'
include Egison
twin_primes = match_stream(Prime) {
with(List.(*_, _p, __("p + 2"), *_)) {
[p, p + 2]
}
}
p twin_primes.take(10)
#=>[[3, 5], [5, 7], [11, 13], [17, 19], [29, 31], [41, 43], [59, 61], [71, 73], [101, 103], [107, 109]]We can also enumerate prime triplets using and-patterns and or-patterns effectively.
prime_triplets = match_stream(Prime) {
with(List.(*_, _p, And(Or(__("p + 2"), __("p + 4")), _m), __("p + 6"), *_)) {
[p, m, p + 6]
}
}
p prime_triplets.take(10)
#=>[[5, 7, 11], [7, 11, 13], [11, 13, 17], [13, 17, 19], [17, 19, 23], [37, 41, 43], [41, 43, 47], [67, 71, 73], [97, 101, 103], [101, 103, 107]]Of course, we can also patten match against algebraic data types as ordinary functional programming languages. Here is a simple example.
class User < Struct.new(:name, :gender, :married, :doctor, :professor)
def greet
match(self) do
with(User.(_name, _, _, _, true)) { "Hello, Prof. #{name}!" }
with(User.(_name, _, _, true, _)) { "Hello, Dr. #{name}!" }
with(User.(_name, :female, true, _, _)) { "Hello, Mrs. #{name}!" }
with(User.(_name, :female, _, _, _)) { "Hello, Ms. #{name}!" }
with(User.(_name, :male, _, _, _)) { "Hello, Mr. #{name}!" }
with(User.(_name, _, _, _, _)) { "Hello, #{name}!" }
end
end
end
u1 = User.new("Egi", :male, true, false, false)
p(u1.greet)#=>"Hello, Mr. Egi!"
u2 = User.new("Nanaka", :girl, false, false, false)
p(u2.greet)#=>"Hello, Nanaka!"
u3 = User.new("Hirai", :male, true, true, false)
p(u3.greet)#=>"Hello, Dr. Hirai!"
u4 = User.new("Hagiya", :male, true, true, true)
p(u4.greet)#=>"Hello, Prof. Hagiya!"You can find more demonstrations in the sample directory.
If you get to love the above pattern-matching, please try the Egison programming language, too. Egison is the pattern-matching oriented, purely functional programming language. Actually, the original pattern-matching system of Egison is more powerful. For example, we can do following things in the original Egison.
- We can define new pattern-constructors.
- We can modularize useful patterns.
There is a new programming world!
If you get interested in this Gem, please contact Satoshi Egi or tweet to @Egison_Lang.
We will talk about this gem in RubyKaigi 2014!
The license of this library code is BSD. I learned how to implement pattern-matching in Ruby and how to write a gem from the code of the pattern-match gem by Kazuki Tsujimoto. I designed syntax of pattern-matching to go with that gem. This library contains the copy from that gem. The full license text is here.
This project is sponsored by Rakuten, Inc. and Rakuten Institute of Technology.