# Pattern-matching for method arguments in Ruby

**URL:** <https://www.ruby-forum.com/t/pattern-matching-for-method-arguments-in-ruby/75630>\
**Category:** Ruby\
**Created:** [November 11, 2006, 6:28pm UTC](https://www.ruby-forum.com/t/pattern-matching-for-method-arguments-in-ruby/75630 "2006-11-11T18:28:06Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Victor\_SZverokSS\_Sh](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/v/f04885/32.png) [@Victor\_SZverokSS\_Sh](https://www.ruby-forum.com/u/Victor_SZverokSS_Sh)\
**Post date:** [November 11, 2006, 6:28pm UTC](https://www.ruby-forum.com/t/pattern-matching-for-method-arguments-in-ruby/75630/1 "2006-11-11T18:28:06Z")

</div>

Hi all.

I think, pattern-matching for function argument is a rather good idea.  
I know, it it someway embodied into Multiple Dispatch Library[1], but it  
has  
some major drawbacks:

1. it is not Ruby:  
#normal method:  
def reverse(array)  
end

#multiple dispatched method:  
multi(:reverse, []){}  
multi(:reverse, Array){}

1. it is checking for argument _types_, not their “shape” (read further  
to  
understand it)

2. it does only arguments checking, not their conversion, for ex., lists  
can’t be divided into “head” and “tail” (therefore my note about  
“arguments  
shape”: you can’t say something like "argument 1 should be array with  
first  
element being hash).

3. it has no progress since v0.1 (December 21, 2005)

\*\*\* And Now for Something Completely Different!!!\*\*\*  
(Oh, is it me citing Monthy _Python_? Shame on me! Chunky bacon! Chunky  
bacon!)

The proposed code is rather proof-of-concept than library (it has some  
ugly  
fragments), but it is working.

Some examples:

“sample”.pattern\_match(“sample”) #=\> []  
“sample”.pattern\_match(“other sample”) #=\>nil  
#array of remembered matches returned.  
#Empty array means “pattern matched, but nothing to remember”  
#Ok, it’s very trivial. More complex:

“sample”.pattern\_match(String) #=\> [“sample”]  
#The example above checked, if “sample” match String pattern,  
#and remembers matched values

“sample”.pattern\_match(Object) #=\> [“sample”]  
“sample”.pattern\_match(Numeric) #=\> nil

#life becames more intersting:  
[“sample”, 1].pattern\_match([“sample”, 1]) #=\> []  
[“sample”, 1].pattern\_match([“sample”, Numeric]) #=\> [1]  
[“sample”, 1].pattern\_match([String, Numeric]) #=\> [“sample”, 1]

#patterns can be nested:  
[“sample”, [1,2]].pattern\_match([String, [Numeric, Numeric]]) #=\>  
[“sample”,  
1, 2]

#hashes also work  
{“sample” =\> [1,2]}.pattern\_match({String =\> Array}) #=\> [“sample”,  
[1,2]]  
{“sample” =\> [1,2]}.pattern\_match({String =\> [Integer, Integer]}) #=\>  
[“sample”, 1, 2]

#splat operator used to say “tail”, “the rest of array”  
[“sample”, 1].pattern\_match([String, \*Object]) #=\> [“sample”, [1]]  
[“sample”, 1,2,3,4].pattern\_match([String, \*Object]) #=\> [“sample”,  
[1,2,3,4]]  
[“sample”].pattern\_match([String, \*Object]) #=\> [“sample”, []]

## And, finally, the usage of above for multiple dispatch:

def reverse(\*arg)  
arg.pmatch([]){  
[]  
} ||  
arg.pmatch([Object, \*Object]){|first, tail|  
reverse(tail) + [first]  
} ||  
arg.nomatch!  
end

p reverse([1,2,3]) #=\> [3,2,1]  
p reverse("") #in `Array#nomatch!’: Can’t find match for [""]  
(TypeError)

Isn’t it SEXY? 🙂

More complex example:

def printTag(\*arg)  
arg.pmatch(String){|tag|  
printTag(tag, [])  
} ||  
arg.pmatch(String, []){|tag|  
“\<#{tag}\>”  
} ||  
arg.pmatch(String, Array){|tag, attr|  
“\<#{tag}#{printAttrs(attr)}\>”  
} ||  
arg.nomatch!  
end

def printAttrs(\*arg)  
arg.pmatch([]){  
“”  
} ||  
arg.pmatch([[String, String], \*Object]){|key, value, tail|  
" #{key}=’#{value}’" + printAttrs(tail)  
} ||  
arg.nomatch!  
end

puts printTag(‘tag’) #=\> “”  
puts printTag(‘tag’, [[‘a’,‘b’], [‘c’,‘d’]]) #=\> “”

## No more word. Here’s the code (a bit ugly, sorry):

class Object  
def pattern\_match(pattern)  
pattern.match\_by(self)  
end  
end

class Object  
def match\_by(obj)  
obj == self && []  
end  
end

class Class  
def match\_by(obj)  
obj.kind\_of?(self) && [obj]  
end  
end

class Array  
def match\_by(obj)  
if !self.empty? && self.last == :splat  
head, tail = obj[0…self.size-1], obj[self.size-1…-1]  
res = head.pattern\_match(self[0…-2])  
res ? res + [tail] : nil  
else  
obj.instance\_of?(Array) &&  
obj.size == self.size &&  
self.zip(obj).inject([]){|res, po|  
p,o = po;  
r = o.pattern\_match§;  
break unless r;  
res += r  
}  
end  
end  
end

class Hash  
def match\_by(obj)  
return false unless obj.instance\_of?(Hash) && obj.size == self.size  
res = []  
self.each{|k, v|  
pair = obj.detect{|ko, vo|  
rk = ko.pattern\_match(k)  
rv = vo.pattern\_match(v)  
if rk && rv  
res += rk + rv  
true  
else  
false  
end

```
  }
  return false unless pair
}
res

```

end  
end

class Class  
def to\_splat; [:splat] end  
end

class Array  
def pmatch(\*patterns, &block)  
res = self.pattern\_match(patterns)  
if res  
if block  
block[\*res]  
else  
res  
end  
else  
nil  
end  
end

## def nomatch! raise TypeError, “Can’t find match for #{self.inspect}” end end

That’s all.  
Thanks.

V.
