Pattern-matching for method arguments in Ruby

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? :slight_smile:

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.